2756 lines
118 KiB
Python
2756 lines
118 KiB
Python
import serial
|
||
import threading
|
||
import time
|
||
import re
|
||
import binascii
|
||
import queue
|
||
import os
|
||
from PyQt5.QtCore import QObject, pyqtSignal, QDateTime, QTimer
|
||
from sms_utils import text_to_ucs2, ucs2_to_text, is_chinese_text, format_phone_number
|
||
|
||
# Import serial.tools.list_ports for port detection
|
||
import serial.tools.list_ports
|
||
|
||
class LTEManager(QObject):
|
||
# Signals
|
||
sms_received = pyqtSignal(str, str, str) # sender, timestamp, message
|
||
call_received = pyqtSignal(str) # caller number
|
||
call_ended = pyqtSignal(str) # duration
|
||
status_changed = pyqtSignal(str) # status message
|
||
dtmf_received = pyqtSignal(str) # DTMF tone
|
||
pcm_audio_status = pyqtSignal(bool) # PCM audio registration status (True=registered, False=unregistered)
|
||
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.at_serial = None
|
||
self.nmea_serial = None
|
||
self.at_port = ""
|
||
self.nmea_port = ""
|
||
self.at_baudrate = 115200
|
||
self.nmea_baudrate = 9600
|
||
self.connected = False
|
||
self.running = False # Flag to control the read thread
|
||
self.read_thread = None
|
||
self.response_queue = queue.Queue()
|
||
self.lock = threading.Lock()
|
||
|
||
# Command cache
|
||
self.command_cache = {}
|
||
|
||
# Module information
|
||
self.imei = ""
|
||
self.imsi = ""
|
||
self.model = ""
|
||
self.manufacturer = ""
|
||
self.firmware = ""
|
||
self.phone_number = ""
|
||
self.carrier = ""
|
||
self.network_type = ""
|
||
self.signal_strength = ""
|
||
|
||
# Call status
|
||
self.in_call = False
|
||
self.call_connected = False # 标记通话是否已经接通(区分来电振铃和通话接通)
|
||
self.call_number = ""
|
||
self.call_notification_sent = False # Flag to track if we've already notified about this call
|
||
self.call_states = {
|
||
0: "正在进行", # active
|
||
1: "保持", # hold
|
||
2: "拨号中", # dialing (MO)
|
||
3: "振铃中", # alerting (MO)
|
||
4: "来电中", # incoming (MT)
|
||
5: "等待中" # waiting (MT)
|
||
}
|
||
|
||
# SMS handling
|
||
self.waiting_for_sms_content = False
|
||
self.pending_sms_sender = None
|
||
self.pending_sms_timestamp = None
|
||
|
||
# 长短信处理
|
||
self.concat_sms_parts = {} # 用于存储长短信的各个部分
|
||
self.concat_sms_timeout = 30 # 长短信合并超时时间(秒)
|
||
|
||
# 启动定期清理超时长短信的定时器
|
||
self.cleanup_timer = QTimer()
|
||
self.cleanup_timer.timeout.connect(self._cleanup_old_sms_parts)
|
||
self.cleanup_timer.start(10000) # 每10秒清理一次
|
||
|
||
# 添加AT命令日志文件路径
|
||
self.at_log_file = None
|
||
self._setup_at_log_file()
|
||
|
||
def _setup_at_log_file(self):
|
||
"""设置AT命令日志文件"""
|
||
try:
|
||
# 确保.LTE目录存在
|
||
home_dir = os.path.expanduser("~")
|
||
lte_dir = os.path.join(home_dir, ".LTE")
|
||
if not os.path.exists(lte_dir):
|
||
os.makedirs(lte_dir)
|
||
|
||
# 清理旧日志文件(保留最近7天的日志)
|
||
self._cleanup_old_log_files(lte_dir)
|
||
|
||
# 创建基于日期的日志文件 - 使用time模块获取当前日期
|
||
today = time.strftime("%Y-%m-%d")
|
||
log_file_path = os.path.join(lte_dir, f"at_commands_{today}.log")
|
||
|
||
# 以追加模式打开日志文件
|
||
self.at_log_file = open(log_file_path, "a", encoding="utf-8")
|
||
print(f"AT命令日志文件已创建: {log_file_path}")
|
||
|
||
# 记录会话开始 - 使用time模块获取当前时间
|
||
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
|
||
self.at_log_file.write(f"\n===== LTE管理器会话开始 {timestamp} =====\n")
|
||
self.at_log_file.flush()
|
||
|
||
return True
|
||
except Exception as e:
|
||
print(f"创建AT命令日志文件失败: {str(e)}")
|
||
self.at_log_file = None
|
||
return False
|
||
|
||
def _cleanup_old_log_files(self, log_dir, max_days=7):
|
||
"""清理旧的日志文件,只保留最近max_days天的日志"""
|
||
try:
|
||
# 获取当前时间戳
|
||
current_time = time.time()
|
||
# 计算max_days天前的时间戳
|
||
max_age = current_time - (max_days * 24 * 60 * 60)
|
||
|
||
# 遍历日志目录
|
||
for file in os.listdir(log_dir):
|
||
# 只处理AT命令日志文件
|
||
if file.startswith('at_commands_') and file.endswith('.log'):
|
||
file_path = os.path.join(log_dir, file)
|
||
# 获取文件修改时间
|
||
file_time = os.path.getmtime(file_path)
|
||
# 如果文件修改时间早于max_days天前,则删除
|
||
if file_time < max_age:
|
||
os.remove(file_path)
|
||
print(f"已删除旧日志文件: {file}")
|
||
except Exception as e:
|
||
print(f"清理旧日志文件时出错: {str(e)}")
|
||
|
||
def _log_at_interaction(self, command, response=None):
|
||
"""记录AT命令交互"""
|
||
try:
|
||
if self.at_log_file:
|
||
# 使用time模块获取时间戳
|
||
timestamp = time.strftime("%Y-%m-%d %H:%M:%S.") + f"{int(time.time() * 1000) % 1000:03d}"
|
||
if command is not None:
|
||
# 只记录发送的命令
|
||
self.at_log_file.write(f"{timestamp} >>> {command}\n")
|
||
self.at_log_file.flush()
|
||
except Exception as e:
|
||
print(f"记录AT命令时出错: {str(e)}")
|
||
|
||
def _log_response(self, command, response):
|
||
"""单独记录AT命令的响应,避免重复记录命令"""
|
||
try:
|
||
if self.at_log_file:
|
||
# 使用time模块获取时间戳
|
||
timestamp = time.strftime("%Y-%m-%d %H:%M:%S.") + f"{int(time.time() * 1000) % 1000:03d}"
|
||
if response:
|
||
self.at_log_file.write(f"{timestamp} <<< {response}\n")
|
||
self.at_log_file.flush()
|
||
except Exception as e:
|
||
print(f"记录AT命令响应时出错: {str(e)}")
|
||
|
||
def _log_unsolicited(self, response):
|
||
"""记录非请求的响应,使用独立的格式"""
|
||
try:
|
||
if self.at_log_file:
|
||
# 使用time模块获取时间戳
|
||
timestamp = time.strftime("%Y-%m-%d %H:%M:%S.") + f"{int(time.time() * 1000) % 1000:03d}"
|
||
self.at_log_file.write(f"{timestamp} <UNSOLICITED> {response}\n")
|
||
self.at_log_file.flush()
|
||
except Exception as e:
|
||
print(f"记录非请求响应时出错: {str(e)}")
|
||
|
||
def connect(self, port=None, baudrate=115200):
|
||
"""Connect to the LTE module"""
|
||
try:
|
||
# Check if already connected
|
||
if self.is_connected():
|
||
print("Already connected")
|
||
return True
|
||
|
||
# Initialize command cache
|
||
self.command_cache = {}
|
||
|
||
# 重置连接状态
|
||
self.connected = False
|
||
|
||
# Validate port
|
||
if not port:
|
||
# Try to auto-detect port
|
||
port = self._auto_detect_port()
|
||
if not port:
|
||
self.status_changed.emit("Error: No serial port detected")
|
||
return False
|
||
|
||
self.status_changed.emit(f"Connecting to {port}...")
|
||
print(f"尝试连接到端口: {port}, 波特率: {baudrate}")
|
||
|
||
# Make sure any previous connection is properly closed
|
||
try:
|
||
if hasattr(self, 'at_serial') and self.at_serial:
|
||
self.at_serial.close()
|
||
time.sleep(0.5) # Increased delay to give OS more time to release the port
|
||
print(f"已关闭之前的串口连接,等待500ms")
|
||
except Exception as e:
|
||
print(f"Warning: Error closing previous serial connection: {str(e)}")
|
||
|
||
# Connect to the serial port
|
||
try:
|
||
print(f"打开串口: {port}")
|
||
self.at_serial = serial.Serial(
|
||
port=port,
|
||
baudrate=baudrate,
|
||
bytesize=serial.EIGHTBITS,
|
||
parity=serial.PARITY_NONE,
|
||
stopbits=serial.STOPBITS_ONE,
|
||
timeout=2.0, # Increased timeout
|
||
write_timeout=1.0,
|
||
xonxoff=False,
|
||
rtscts=False,
|
||
dsrdtr=False
|
||
)
|
||
print(f"串口已打开,初始化响应队列")
|
||
|
||
# Check if the port is open
|
||
if not self.at_serial.is_open:
|
||
print(f"错误: 串口未能打开")
|
||
return False
|
||
|
||
# Initialize the response queue
|
||
self.response_queue = queue.Queue()
|
||
|
||
# Clear any pending data
|
||
self.at_serial.reset_input_buffer()
|
||
self.at_serial.reset_output_buffer()
|
||
print(f"串口缓冲区已重置")
|
||
|
||
# Set the running flag before starting the thread
|
||
self.running = True
|
||
|
||
# Start the read thread
|
||
self.read_thread = threading.Thread(target=self._read_thread, daemon=True)
|
||
self.read_thread.start()
|
||
print(f"读取线程已启动")
|
||
|
||
# 确保日志文件已创建 (但不重复创建)
|
||
if not self.at_log_file:
|
||
print("创建AT命令日志文件...")
|
||
self._setup_at_log_file()
|
||
|
||
# Wait for the thread to start reading
|
||
time.sleep(0.2)
|
||
|
||
# 直接尝试写入一个空行到串口,测试是否可用
|
||
try:
|
||
self.at_serial.write(b'\r\n')
|
||
time.sleep(0.1)
|
||
print("发送空行成功")
|
||
except Exception as e:
|
||
print(f"发送空行失败: {str(e)}")
|
||
|
||
# Send AT command multiple times to ensure connection
|
||
print(f"发送AT测试命令...")
|
||
response = ""
|
||
for attempt in range(3):
|
||
try:
|
||
# 直接使用串口写入,而不是send_at_command
|
||
if attempt == 0:
|
||
self.at_serial.write(b'AT\r\n')
|
||
print("直接写入AT命令")
|
||
time.sleep(0.2)
|
||
# 读取响应
|
||
data = self.at_serial.read(self.at_serial.in_waiting or 100)
|
||
if data:
|
||
response = data.decode('utf-8', errors='replace')
|
||
print(f"直接读取响应: {response}")
|
||
if "OK" in response:
|
||
print("直接通信成功!")
|
||
self.connected = True
|
||
break
|
||
else:
|
||
# 使用正常方法发送命令
|
||
response = self.send_at_command("AT", timeout=2.0, retries=1)
|
||
print(f"AT命令尝试 {attempt+1}/3 响应: {response}")
|
||
if "OK" in response or self.connected:
|
||
break
|
||
except Exception as e:
|
||
print(f"AT命令尝试 {attempt+1} 失败: {str(e)}")
|
||
time.sleep(0.5)
|
||
|
||
if self.connected: # 使用self.connected标志,该标志在send_at_command中设置
|
||
self.status_changed.emit(f"Connected to {port}")
|
||
print(f"成功连接到 {port}")
|
||
self.port = port
|
||
self.baudrate = baudrate
|
||
|
||
# Configure the module
|
||
print(f"开始配置模块...")
|
||
self._configure_module()
|
||
|
||
return True
|
||
else:
|
||
self.running = False # Stop the read thread
|
||
self.status_changed.emit("Error: Module not responding")
|
||
print(f"错误: 模块未响应, 响应内容: {response}")
|
||
if hasattr(self, 'at_serial') and self.at_serial and self.at_serial.is_open:
|
||
self.at_serial.close()
|
||
return False
|
||
|
||
except Exception as e:
|
||
self.running = False # Make sure thread stops if an error occurs
|
||
self.status_changed.emit(f"Error connecting: {str(e)}")
|
||
print(f"连接错误: {str(e)}")
|
||
return False
|
||
|
||
except Exception as e:
|
||
self.status_changed.emit(f"Error in connect: {str(e)}")
|
||
print(f"连接过程中发生错误: {str(e)}")
|
||
return False
|
||
|
||
def disconnect(self):
|
||
"""Disconnect from the LTE module"""
|
||
try:
|
||
if self.is_connected():
|
||
# Stop the read thread
|
||
self.running = False
|
||
if self.read_thread and self.read_thread.is_alive():
|
||
try:
|
||
self.read_thread.join(1.0) # Wait for thread to finish, timeout after 1 second
|
||
except Exception as e:
|
||
print(f"Warning: Error waiting for read thread: {str(e)}")
|
||
|
||
# Log disconnection
|
||
if self.at_log_file:
|
||
# 使用time模块获取时间戳
|
||
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
|
||
self.at_log_file.write(f"\n===== LTE管理器会话结束 {timestamp} =====\n\n")
|
||
self.at_log_file.flush()
|
||
self.at_log_file.close()
|
||
print(f"AT命令日志文件已关闭: {self.at_log_file.name}")
|
||
self.at_log_file = None
|
||
|
||
# Close the serial port
|
||
if hasattr(self, 'at_serial') and self.at_serial:
|
||
self.at_serial.close()
|
||
|
||
self.connected = False
|
||
self.status_changed.emit("Disconnected")
|
||
return True
|
||
return False
|
||
except Exception as e:
|
||
self.status_changed.emit(f"Error disconnecting: {str(e)}")
|
||
return False
|
||
|
||
def is_connected(self):
|
||
"""Check if connected to the LTE module"""
|
||
return self.connected
|
||
|
||
def send_at_command(self, command, timeout=2.0, retries=2, use_cache=False):
|
||
"""发送AT命令并等待响应"""
|
||
# 如果使用缓存并且命令有缓存
|
||
if use_cache and command in self.command_cache:
|
||
cache_time, cache_result = self.command_cache[command]
|
||
# 检查缓存是否过期 (500ms)
|
||
if time.time() - cache_time < 0.5:
|
||
# 记录使用了缓存
|
||
print(f"使用缓存结果: {command}")
|
||
self._log_at_interaction(command, f"[CACHED] {cache_result}")
|
||
return cache_result
|
||
|
||
# 初始化重试计数器
|
||
retry_count = 0
|
||
|
||
print(f"发送AT命令: {command}, 超时: {timeout}秒, 最大重试次数: {retries}")
|
||
|
||
while retry_count < retries:
|
||
try:
|
||
# 检查串口是否已打开(不检查self.connected标志)
|
||
if not hasattr(self, 'at_serial') or not self.at_serial or not self.at_serial.is_open:
|
||
error_msg = "ERROR: Serial port not open"
|
||
print(f"命令发送失败: {error_msg}")
|
||
self._log_at_interaction(command, error_msg)
|
||
return error_msg
|
||
|
||
# 清空输入缓冲区
|
||
try:
|
||
self.at_serial.reset_input_buffer()
|
||
print(f"输入缓冲区已清空")
|
||
except Exception as e:
|
||
print(f"清空输入缓冲区失败: {str(e)}")
|
||
|
||
# 记录发送的AT命令
|
||
self._log_at_interaction(command, None)
|
||
|
||
# 发送命令
|
||
cmd = command + "\r\n"
|
||
bytes_written = self.at_serial.write(cmd.encode())
|
||
print(f"发送命令: {command},已写入 {bytes_written} 字节")
|
||
|
||
# 确保命令已发送
|
||
self.at_serial.flush()
|
||
print(f"命令已刷新到设备")
|
||
|
||
# 等待并读取响应
|
||
response = self._read_serial(timeout)
|
||
print(f"收到命令响应: {response}")
|
||
|
||
# 检查响应
|
||
if "ERROR" in response:
|
||
# 记录错误响应
|
||
self._log_response(command, response)
|
||
|
||
# 区分不同类型的错误
|
||
# 1. 查询命令返回ERROR - 这通常表示命令不支持,不需要重试
|
||
if command.endswith("=?") or command.endswith("?"):
|
||
print(f"命令不支持: {command} -> {response}")
|
||
# 将响应缓存并返回,不重试
|
||
self.command_cache[command] = (time.time(), response)
|
||
return response
|
||
|
||
# 2. CME ERROR或CMS ERROR - 这是带错误代码的特定错误,说明命令被识别但执行失败
|
||
if "+CME ERROR:" in response or "+CMS ERROR:" in response:
|
||
print(f"命令执行错误: {command} -> {response}")
|
||
# 如果是特定错误代码,不需要重试
|
||
self.command_cache[command] = (time.time(), response)
|
||
return response
|
||
|
||
# 3. 普通ERROR - 可能需要重试的通信问题
|
||
print(f"命令执行错误: {command} -> {response}, 重试 {retry_count+1}/{retries}")
|
||
retry_count += 1
|
||
time.sleep(0.5) # 出错时延迟后重试
|
||
continue
|
||
else:
|
||
# 命令成功,记录并缓存响应
|
||
self._log_response(command, response)
|
||
print(f"命令执行成功: {command}")
|
||
|
||
# 如果这是AT命令并且响应包含OK,则设置connected标志
|
||
if command == "AT" and "OK" in response:
|
||
self.connected = True
|
||
print("连接状态已设置为已连接")
|
||
|
||
# 缓存响应结果
|
||
self.command_cache[command] = (time.time(), response)
|
||
return response
|
||
|
||
except Exception as e:
|
||
print(f"命令 {command} 执行时出错: {str(e)}")
|
||
error_msg = f"ERROR: {str(e)}"
|
||
retry_count += 1
|
||
time.sleep(0.5) # 出错时延迟后重试
|
||
continue
|
||
|
||
# 达到最大重试次数后返回错误
|
||
print(f"命令 {command} 已达到最大重试次数 {retries},放弃执行")
|
||
return f"ERROR: Max retries ({retries}) exceeded"
|
||
|
||
def _read_thread(self):
|
||
"""Thread function to continuously read from serial port"""
|
||
buffer = ""
|
||
|
||
print("Serial read thread started")
|
||
while self.running:
|
||
if not self.at_serial or not self.at_serial.is_open:
|
||
time.sleep(0.1)
|
||
continue
|
||
|
||
try:
|
||
# Read data from serial port
|
||
if self.at_serial.in_waiting > 0:
|
||
data = self.at_serial.read(self.at_serial.in_waiting)
|
||
if data:
|
||
text = data.decode('utf-8', errors='replace')
|
||
buffer += text
|
||
|
||
# Process complete lines
|
||
while '\r\n' in buffer:
|
||
line, buffer = buffer.split('\r\n', 1)
|
||
line = line.strip()
|
||
|
||
if not line:
|
||
continue
|
||
|
||
# Process unsolicited responses
|
||
self._process_unsolicited(line)
|
||
|
||
# Add to response queue for command responses
|
||
self.response_queue.put(line)
|
||
else:
|
||
# No data available, sleep briefly to reduce CPU usage
|
||
time.sleep(0.01)
|
||
except Exception as e:
|
||
print(f"Serial read error: {str(e)}")
|
||
time.sleep(0.1)
|
||
|
||
print("Serial read thread stopped")
|
||
|
||
def _read_serial(self, timeout=5.0):
|
||
"""读取串口响应,直到超时或收到完整响应"""
|
||
start_time = time.time()
|
||
response = []
|
||
command_echo_received = False
|
||
|
||
print(f"等待AT命令响应,最大超时时间: {timeout}秒")
|
||
|
||
# 等待响应,直到超时
|
||
while time.time() - start_time < timeout:
|
||
try:
|
||
# 使用queue.get()获取响应行
|
||
line = self.response_queue.get(timeout=0.2)
|
||
print(f"收到响应行: {line}")
|
||
|
||
# 检查是否是命令回显(某些模块会回显命令)
|
||
if not command_echo_received and line.startswith("AT"):
|
||
command_echo_received = True
|
||
# 可以选择跳过命令回显行,不添加到响应中
|
||
continue
|
||
|
||
response.append(line)
|
||
|
||
# 检查是否为完整响应的各种情况
|
||
# 1. 标准的OK结束
|
||
if line == "OK":
|
||
print("收到完整响应标识,结束等待")
|
||
break
|
||
|
||
# 2. ERROR结束
|
||
elif line == "ERROR" or "+CMS ERROR:" in line or "+CME ERROR:" in line:
|
||
print("收到完整响应标识,结束等待")
|
||
break
|
||
|
||
# 3. 没有正常的结束标记,但对于某些特殊响应需要特殊处理
|
||
# 例如,对于COPS?命令如果收到了+COPS:行,可能不需要等待OK
|
||
elif "+COPS:" in line and "OK" not in response:
|
||
# 继续等待OK,不直接退出
|
||
pass
|
||
|
||
# 4. 对于返回消息体的命令,等待足够长的时间确保收到完整消息
|
||
# 特别是短信内容、通话状态等
|
||
elif "+CMGR:" in line or "+CMGL:" in line or "+CLCC:" in line:
|
||
# 继续等待,这些命令通常有多行响应
|
||
pass
|
||
|
||
except queue.Empty:
|
||
# 如果队列为空,检查是否已有足够的响应内容
|
||
# 如果已经收到至少一行,且最后一行包含某些特定结束标记,也可以结束等待
|
||
if response:
|
||
# 检查最后一行是否是某种结束标记
|
||
last_line = response[-1]
|
||
if last_line in ["OK", "ERROR"] or "+CMS ERROR:" in last_line or "+CME ERROR:" in last_line:
|
||
print("在队列等待期间确认已收到完整响应")
|
||
break
|
||
|
||
# 对于某些命令,没有明确结束标记,但收到特定响应后短时间内没有更多响应,也可视为完成
|
||
# 例如,AT+CSQ后只有一行+CSQ:响应,但没有OK
|
||
if ("+CSQ:" in last_line or "+CREG:" in last_line or "+CGREG:" in last_line) and \
|
||
time.time() - start_time > 1.0: # 等待至少1秒以确保无更多响应
|
||
print("已收到关键响应行且无后续内容,视为完成")
|
||
break
|
||
|
||
# 如果没有收到任何响应,返回超时错误
|
||
if not response:
|
||
print("读取超时,未收到任何响应")
|
||
return "ERROR: Read timeout"
|
||
|
||
# 合并所有响应行并返回
|
||
full_response = "\n".join(response)
|
||
return full_response
|
||
|
||
def _process_unsolicited(self, line):
|
||
"""处理非请求响应"""
|
||
# 不把AT命令及其响应作为unsolicited response处理
|
||
if line.startswith("AT") or line == "OK" or line == "ERROR" or line.startswith("+CSQ") or line.startswith("+CREG") or line.startswith("+CGREG"):
|
||
# 跳过可能的命令回显或常见查询响应
|
||
return
|
||
|
||
# 对于真正的非请求通知,记录并处理
|
||
self._log_unsolicited(line)
|
||
|
||
# Incoming call
|
||
if line.startswith("RING"):
|
||
self.status_changed.emit("Incoming call")
|
||
self.in_call = True
|
||
# 设置为未接听状态
|
||
self.call_connected = False
|
||
# Reset notification flag on new RING
|
||
self.call_notification_sent = False
|
||
|
||
# Caller ID
|
||
elif "+CLIP:" in line:
|
||
match = re.search(r'\+CLIP: "([^"]+)"', line)
|
||
if match:
|
||
number = match.group(1)
|
||
self.call_number = number
|
||
|
||
# Only emit the signal if we haven't sent a notification for this call yet
|
||
if not self.call_notification_sent and self.in_call:
|
||
self.call_received.emit(number)
|
||
self.call_notification_sent = True
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - Call notification sent for {number}")
|
||
|
||
# Call ended
|
||
elif "NO CARRIER" in line:
|
||
self.in_call = False
|
||
self.call_connected = False
|
||
self.call_notification_sent = False # Reset the flag when call ends
|
||
self.status_changed.emit("Call ended")
|
||
|
||
# 记录通话结束日志,方便调试
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - Call ended, NO CARRIER detected")
|
||
|
||
# 通话结束时取消PCM音频注册
|
||
self._ensure_pcm_audio_unregistered()
|
||
|
||
# 发送通话结束信号
|
||
self.call_ended.emit("Call ended")
|
||
|
||
# Voice call begin - 这是通话实际建立的时间点
|
||
elif "VOICE CALL: BEGIN" in line:
|
||
# 设置通话状态为活动
|
||
self.in_call = True
|
||
# 设置为已接通状态
|
||
self.call_connected = True
|
||
|
||
# 记录日志
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 通话已建立 (VOICE CALL: BEGIN)")
|
||
self.status_changed.emit("Call in progress")
|
||
|
||
# 先确保任何可能存在的PCM注册已取消
|
||
self._unregister_pcm_audio()
|
||
|
||
# 短暂延迟后再注册PCM音频,确保模块已稳定
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 延迟100ms后注册PCM音频")
|
||
time.sleep(0.1) # 先延迟一小段时间
|
||
|
||
# 开始注册PCM音频
|
||
self._register_pcm_audio()
|
||
|
||
# Voice call end
|
||
elif "VOICE CALL: END:" in line:
|
||
self.in_call = False
|
||
self.call_connected = False
|
||
match = re.search(r'VOICE CALL: END: (\d+)', line)
|
||
duration = "0"
|
||
if match:
|
||
duration = match.group(1)
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - Call ended, duration: {duration}")
|
||
else:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - Call ended, no duration info")
|
||
|
||
# 记录详细日志,包括通话持续时间
|
||
call_minutes = int(duration) // 60
|
||
call_seconds = int(duration) % 60
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 通话结束,持续时间: {call_minutes}分{call_seconds}秒")
|
||
|
||
# 首先取消PCM音频注册,然后才发送通话结束信号
|
||
# 这样可以确保PCM音频在通话结束信号处理前已经被取消
|
||
if self._ensure_pcm_audio_unregistered():
|
||
# 在成功取消注册后发送信号
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频已取消注册,发送通话结束信号")
|
||
# 使用threading.Timer代替QTimer,避免线程问题
|
||
threading.Timer(0.2, lambda: self.call_ended.emit(duration)).start()
|
||
else:
|
||
# 即使取消注册失败,也要发送通话结束信号
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频取消注册失败,仍发送通话结束信号")
|
||
# 使用threading.Timer代替QTimer,避免线程问题
|
||
threading.Timer(0.2, lambda: self.call_ended.emit(duration)).start()
|
||
|
||
# Missed call
|
||
elif "MISSED_CALL:" in line:
|
||
match = re.search(r'MISSED_CALL: ([^\r\n]+)', line)
|
||
if match:
|
||
missed_info = match.group(1)
|
||
self.status_changed.emit(f"Missed call: {missed_info}")
|
||
|
||
# Extract phone number from missed call info
|
||
# Format is typically "HH:MMAM/PM PHONENUMBER"
|
||
parts = missed_info.strip().split()
|
||
if len(parts) >= 2:
|
||
missed_number = parts[-1] # Last part should be the phone number
|
||
# Signal call ended to stop ringtone
|
||
self.call_ended.emit("Missed")
|
||
# Also emit missed call signal with the number
|
||
self.call_number = missed_number
|
||
self.status_changed.emit(f"Missed call from {missed_number}")
|
||
|
||
# SMS received (direct content mode)
|
||
elif line.startswith("+CMT:"):
|
||
try:
|
||
# 提取发送者号码和时间戳,用于后续匹配和合并短信
|
||
sender_match = re.search(r'\+CMT: "([^"]*)",[^,]*,"([^"]*)"', line)
|
||
if sender_match:
|
||
sender = sender_match.group(1)
|
||
timestamp = sender_match.group(2)
|
||
|
||
# 解码发送者号码(如果是UCS2编码)
|
||
if sender.startswith("00"):
|
||
try:
|
||
sender = ucs2_to_text(sender)
|
||
except Exception as e:
|
||
print(f"解码发送者号码出错: {str(e)}")
|
||
|
||
# 保存短信头部信息,用于后续处理
|
||
self.pending_sms_sender = sender
|
||
self.pending_sms_timestamp = timestamp
|
||
|
||
# 生成SMS ID用于匹配多段短信
|
||
sms_id = f"{sender}_{timestamp[:10]}"
|
||
|
||
# 检查是否已有相同ID的短信正在处理中
|
||
is_continuation = False
|
||
if sms_id in self.concat_sms_parts:
|
||
# 这是已有短信的后续部分
|
||
is_continuation = True
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 检测到后续短信部分: {sms_id}")
|
||
self.status_changed.emit(f"检测到后续短信部分,来自 {sender}")
|
||
|
||
# 标记等待内容行
|
||
self.waiting_for_sms_content = True
|
||
|
||
# 保存当前短信ID,用于内容行处理
|
||
self.current_sms_id = sms_id
|
||
self.current_is_continuation = is_continuation
|
||
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 收到短信头部,发送者: {sender}, 时间: {timestamp}, ID: {sms_id}")
|
||
else:
|
||
# 无法解析发送者和时间,使用默认处理方式
|
||
if self._is_concatenated_sms(line):
|
||
self._handle_concatenated_sms(line)
|
||
else:
|
||
self._handle_regular_sms(line)
|
||
except Exception as e:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 处理短信头部出错: {str(e)}")
|
||
# 错误时使用旧方法尝试处理
|
||
if self._is_concatenated_sms(line):
|
||
self._handle_concatenated_sms(line)
|
||
else:
|
||
self._handle_regular_sms(line)
|
||
|
||
elif self.waiting_for_sms_content:
|
||
# 这是短信内容行
|
||
self.waiting_for_sms_content = False
|
||
message = line
|
||
|
||
# 检查是否保存了短信ID信息
|
||
sms_id = getattr(self, 'current_sms_id', None)
|
||
is_continuation = getattr(self, 'current_is_continuation', False)
|
||
|
||
# 清除临时属性
|
||
if hasattr(self, 'current_sms_id'):
|
||
del self.current_sms_id
|
||
if hasattr(self, 'current_is_continuation'):
|
||
del self.current_is_continuation
|
||
|
||
try:
|
||
# 检查是否是UCS2编码
|
||
if all(c in "0123456789ABCDEFabcdef" for c in line.replace(" ", "")):
|
||
# 尝试解码UCS2内容
|
||
decoded_content = None
|
||
try:
|
||
decoded_content = ucs2_to_text(line)
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - UCS2内容解码成功: {decoded_content[:50]}...")
|
||
except Exception as decode_error:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - UCS2解码错误: {str(decode_error)}")
|
||
|
||
# 尝试替代解码方法
|
||
try:
|
||
hex_bytes = binascii.unhexlify(line.replace(" ", ""))
|
||
decoded_content = hex_bytes.decode('utf-16-be', errors='replace')
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 替代解码成功: {decoded_content[:50]}...")
|
||
except Exception as alt_error:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 替代解码也失败: {str(alt_error)}")
|
||
|
||
# 如果是长短信的一部分(根据特定特征判断)
|
||
is_long_message_part = False
|
||
if "62117ED94F6053D14E86957F6587672C" in line:
|
||
is_long_message_part = True
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 检测到长短信特征")
|
||
elif decoded_content and "https://" in decoded_content:
|
||
is_long_message_part = True
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 检测到URL内容,视为长短信")
|
||
|
||
# 处理长短信
|
||
if is_long_message_part or is_continuation:
|
||
# 处理为长短信的一部分
|
||
self._process_long_message_part(self.pending_sms_sender, self.pending_sms_timestamp, line, decoded_content, sms_id)
|
||
else:
|
||
# 常规短信处理,直接发送解码后的内容
|
||
message = decoded_content if decoded_content else line
|
||
self.sms_received.emit(
|
||
self.pending_sms_sender,
|
||
self.pending_sms_timestamp,
|
||
message
|
||
)
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 发送常规短信到UI")
|
||
else:
|
||
# 非UCS2编码,直接发送
|
||
self.sms_received.emit(
|
||
self.pending_sms_sender,
|
||
self.pending_sms_timestamp,
|
||
message
|
||
)
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 发送纯文本短信到UI")
|
||
except Exception as e:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 处理短信内容时出错: {str(e)}")
|
||
# 出错时尝试直接发送原始内容
|
||
self.sms_received.emit(
|
||
self.pending_sms_sender,
|
||
self.pending_sms_timestamp,
|
||
f"[解码错误] {message[:100]}..."
|
||
)
|
||
|
||
# 清除待处理短信数据
|
||
self.pending_sms_sender = None
|
||
self.pending_sms_timestamp = None
|
||
|
||
# SMS received (index mode)
|
||
elif line.startswith("+CMTI:"):
|
||
match = re.search(r'\+CMTI: "([^"]+)",(\d+)', line)
|
||
if match:
|
||
storage, index = match.group(1), match.group(2)
|
||
self.status_changed.emit(f"New SMS at index {index}")
|
||
# Fetch SMS content
|
||
self._fetch_sms(storage, index)
|
||
|
||
# DTMF tone received
|
||
elif "+RXDTMF:" in line:
|
||
match = re.search(r'\+RXDTMF: (\d)', line)
|
||
if match:
|
||
tone = match.group(1)
|
||
self.dtmf_received.emit(tone)
|
||
|
||
# SMS full
|
||
elif "+SMS FULL" in line:
|
||
self.status_changed.emit("SMS storage full. Please delete some messages.")
|
||
|
||
def _process_long_message_part(self, sender, timestamp, content, decoded_content, sms_id):
|
||
"""处理长短信的一部分,支持追加入库功能"""
|
||
try:
|
||
# 移除空格
|
||
content = content.replace(" ", "")
|
||
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 处理长短信部分,ID: {sms_id}, 内容长度: {len(content)}")
|
||
|
||
# 特殊格式检测
|
||
is_special_format = "62117ED94F6053D14E86957F6587672C" in content
|
||
|
||
# 如果没有解码后的内容,尝试解码
|
||
if not decoded_content:
|
||
try:
|
||
decoded_content = ucs2_to_text(content)
|
||
except Exception as e:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 解码UCS2内容出错: {str(e)}")
|
||
try:
|
||
# 尝试替代解码方法
|
||
hex_bytes = binascii.unhexlify(content)
|
||
decoded_content = hex_bytes.decode('utf-16-be', errors='replace')
|
||
except Exception as alt_e:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 替代解码方法失败: {str(alt_e)}")
|
||
decoded_content = f"[无法解码] {content[:50]}..."
|
||
|
||
# 提取URL (如果有)
|
||
url = None
|
||
if is_special_format and decoded_content:
|
||
# 尝试从特殊格式中提取URL
|
||
try:
|
||
url_match = re.search(r':(https?://[^\s]+)', decoded_content)
|
||
if url_match:
|
||
url = url_match.group(1)
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 从特殊格式中提取URL: {url}")
|
||
except Exception as url_e:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 从特殊格式提取URL失败: {str(url_e)}")
|
||
|
||
# 如果没有提取到URL但有解码后的内容,尝试从普通文本中提取
|
||
if not url and decoded_content:
|
||
url_match = re.search(r'(https?://[^\s]+)', decoded_content)
|
||
if url_match:
|
||
url = url_match.group(1)
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 从文本中提取URL: {url}")
|
||
|
||
# 初始化或更新长短信记录
|
||
if sms_id not in self.concat_sms_parts:
|
||
# 提取消息前缀(如果有)
|
||
prefix = "消息"
|
||
if decoded_content and ":" in decoded_content:
|
||
prefix_match = re.match(r'^([^:]+):', decoded_content)
|
||
if prefix_match:
|
||
prefix = prefix_match.group(1).strip()
|
||
|
||
# 创建新的长短信记录
|
||
self.concat_sms_parts[sms_id] = {
|
||
'sender': sender,
|
||
'timestamp': timestamp,
|
||
'parts': [],
|
||
'urls': [],
|
||
'received_time': time.time(),
|
||
'prefix': prefix,
|
||
'is_processed': False # 标记是否已处理
|
||
}
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 创建新的长短信记录: {sms_id}")
|
||
|
||
# 更新长短信记录
|
||
sms_record = self.concat_sms_parts[sms_id]
|
||
|
||
# 添加解码后的内容到parts
|
||
if decoded_content and decoded_content not in sms_record['parts']:
|
||
sms_record['parts'].append(decoded_content)
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 添加第 {len(sms_record['parts'])} 部分到长短信记录")
|
||
|
||
# 添加URL到urls列表(如果有且不重复)
|
||
if url and url not in sms_record['urls']:
|
||
sms_record['urls'].append(url)
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 添加URL到长短信记录: {url}")
|
||
|
||
# 更新接收时间
|
||
sms_record['received_time'] = time.time()
|
||
|
||
# 使用定时器,延迟合并处理长短信(等待其他部分到达)
|
||
# 如果是分条短信,设置较短的延迟;如果是长短信,设置较长的延迟
|
||
delay = 1.5 if len(sms_record['parts']) > 1 else 3.0
|
||
threading.Timer(delay, lambda: self._check_and_merge_sms(sms_id)).start()
|
||
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 设置 {delay} 秒后合并长短信")
|
||
|
||
except Exception as e:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 处理长短信部分时出错: {str(e)}")
|
||
# 出错时尝试直接发送当前部分
|
||
try:
|
||
message = decoded_content if decoded_content else content
|
||
self.sms_received.emit(sender, timestamp, f"[长短信处理错误] {message[:100]}...")
|
||
except Exception as send_e:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 发送错误消息失败: {str(send_e)}")
|
||
|
||
def _check_and_merge_sms(self, sms_id):
|
||
"""检查并合并长短信,支持追加内容到已处理的长短信"""
|
||
if sms_id not in self.concat_sms_parts:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 无法找到长短信记录: {sms_id}")
|
||
return
|
||
|
||
sms_info = self.concat_sms_parts[sms_id]
|
||
|
||
# 检查是否已处理过
|
||
if sms_info.get('is_processed', False):
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 长短信 {sms_id} 已处理过,检查是否有新部分")
|
||
|
||
# 如果已处理过但有新内容(最近3秒内收到的),则追加处理
|
||
current_time = time.time()
|
||
if current_time - sms_info['received_time'] < 3:
|
||
# 有新部分,继续等待更多部分
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 检测到新内容,延迟后再次尝试合并")
|
||
threading.Timer(2.0, lambda: self._check_and_merge_sms(sms_id)).start()
|
||
return
|
||
|
||
# 有新内容需要追加,重新合并并发送更新
|
||
merged_content = self._merge_sms_parts(sms_id)
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 发送更新的长短信内容: {merged_content[:50]}...")
|
||
|
||
# 发送信号,表示这是更新的内容
|
||
self.status_changed.emit(f"更新长短信内容,来自 {sms_info['sender']}")
|
||
self.sms_received.emit(
|
||
sms_info['sender'],
|
||
sms_info['timestamp'],
|
||
merged_content
|
||
)
|
||
|
||
# 记录处理状态
|
||
sms_info['is_processed'] = True
|
||
|
||
# 更新上次处理时间
|
||
sms_info['last_processed'] = current_time
|
||
return
|
||
|
||
# 检查是否有有效部分
|
||
if not sms_info.get('parts', []):
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 长短信 {sms_id} 没有有效部分,跳过合并")
|
||
return
|
||
|
||
# 检查是否收到后续部分的超时(通常1-3秒内应该收到所有部分)
|
||
current_time = time.time()
|
||
time_since_last_part = current_time - sms_info['received_time']
|
||
|
||
# 如果最近2秒内收到新部分,继续等待
|
||
if time_since_last_part < 2.0:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 最近才收到新部分 ({time_since_last_part:.1f}秒前),继续等待")
|
||
return
|
||
|
||
# 超过等待时间,进行合并处理
|
||
merged_content = self._merge_sms_parts(sms_id)
|
||
|
||
# 发送完整消息
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 发送合并后的长短信: {merged_content[:50]}...")
|
||
self.sms_received.emit(
|
||
sms_info['sender'],
|
||
sms_info['timestamp'],
|
||
merged_content
|
||
)
|
||
|
||
# 标记为已处理
|
||
sms_info['is_processed'] = True
|
||
sms_info['last_processed'] = current_time
|
||
|
||
# 记录日志
|
||
self.status_changed.emit(f"已接收完整长短信,来自 {sms_info['sender']}")
|
||
|
||
# 不删除记录,而是保留用于后续追加处理
|
||
# 长短信记录将在清理定时任务中处理
|
||
|
||
def _is_concatenated_sms(self, header_line):
|
||
"""检查是否为长短信"""
|
||
# 检查是否符合长短信格式
|
||
try:
|
||
# 长短信格式通常包含更多逗号分隔的字段
|
||
parts = header_line.split(',')
|
||
# 普通短信头部通常有3个字段,长短信可能有更多
|
||
if len(parts) >= 6:
|
||
return True
|
||
return False
|
||
except Exception as e:
|
||
print(f"检查长短信格式出错: {str(e)}")
|
||
return False
|
||
|
||
def _handle_regular_sms(self, header_line):
|
||
"""处理普通短信"""
|
||
try:
|
||
# 解析SMS头部,格式通常为: +CMT: "sender","","timestamp"
|
||
header_match = re.search(r'\+CMT: "([^"]*)",[^,]*,"([^"]*)"', header_line)
|
||
if header_match:
|
||
sender = header_match.group(1)
|
||
timestamp = header_match.group(2)
|
||
|
||
# 检查发送者是否为UCS2格式(通常以00开头)
|
||
if sender.startswith("00"):
|
||
try:
|
||
sender = ucs2_to_text(sender)
|
||
except Exception as e:
|
||
print(f"解码发送者号码失败: {str(e)}")
|
||
# 解码失败时保留原始格式
|
||
|
||
# 保存发送者和时间信息,等待下一行接收内容
|
||
self.pending_sms_sender = sender
|
||
self.pending_sms_timestamp = timestamp
|
||
self.waiting_for_sms_content = True
|
||
|
||
# 发送状态更新
|
||
self.status_changed.emit(f"收到来自 {sender} 的短信")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 收到来自 {sender} 的短信")
|
||
else:
|
||
# 如果头部格式不匹配,使用默认值
|
||
self.pending_sms_sender = "未知号码"
|
||
self.pending_sms_timestamp = time.strftime("%y/%m/%d,%H:%M:%S")
|
||
self.waiting_for_sms_content = True
|
||
|
||
# 发送状态更新
|
||
self.status_changed.emit("收到短信(无法识别发送者)")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 收到短信(头部格式异常:{header_line})")
|
||
except Exception as e:
|
||
print(f"处理短信头部出错: {str(e)}")
|
||
# 出错时使用默认值
|
||
self.pending_sms_sender = "错误"
|
||
self.pending_sms_timestamp = time.strftime("%y/%m/%d,%H:%M:%S")
|
||
self.waiting_for_sms_content = True
|
||
self.status_changed.emit(f"解析短信头部时出错: {str(e)}")
|
||
|
||
def _handle_concatenated_sms(self, header_line):
|
||
"""处理长短信的头部信息"""
|
||
try:
|
||
# 解析长短信头部,格式可能更复杂
|
||
parts = header_line.split(',')
|
||
if len(parts) < 3:
|
||
# 格式不符合预期,作为普通短信处理
|
||
self._handle_regular_sms(header_line)
|
||
return
|
||
|
||
# 尝试提取发送者和时间戳
|
||
sender_part = parts[0].replace('+CMT: ', '').strip('"')
|
||
# 时间戳位置可能在第3个位置
|
||
timestamp_part = parts[2].strip('"') if len(parts) > 2 else time.strftime("%y/%m/%d,%H:%M:%S")
|
||
|
||
# 检查发送者是否为UCS2格式
|
||
if sender_part.startswith("00"):
|
||
try:
|
||
sender_part = ucs2_to_text(sender_part)
|
||
except Exception as e:
|
||
print(f"解码长短信发送者出错: {str(e)}")
|
||
# 解码失败时保留原始格式
|
||
|
||
# 保存信息等待后续处理
|
||
self.pending_sms_sender = sender_part
|
||
self.pending_sms_timestamp = timestamp_part
|
||
self.waiting_for_sms_content = True
|
||
|
||
# 发送状态更新
|
||
self.status_changed.emit(f"收到来自 {sender_part} 的长短信部分")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 收到来自 {sender_part} 的长短信部分")
|
||
except Exception as e:
|
||
# 出错时尝试作为普通短信处理
|
||
print(f"处理长短信头部出错: {str(e)}")
|
||
self._handle_regular_sms(header_line)
|
||
|
||
def _process_concatenated_sms_part(self, sender, timestamp, content):
|
||
"""处理长短信的内容部分"""
|
||
try:
|
||
# 移除空格
|
||
content = content.replace(" ", "")
|
||
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 处理长短信内容: {content[:50]}...")
|
||
|
||
# 检查是否为特定格式的长短信
|
||
is_special_format = "62117ED94F6053D14E86957F6587672C" in content
|
||
|
||
# 尝试解码内容
|
||
try:
|
||
decoded_content = ucs2_to_text(content)
|
||
print(f"解码内容: {decoded_content[:50]}...")
|
||
except Exception as e:
|
||
print(f"UCS2解码错误: {str(e)}")
|
||
# 如果解码失败,尝试不同方法或直接发送原始内容
|
||
try:
|
||
# 尝试直接从十六进制转换为字节,然后解码
|
||
hex_bytes = binascii.unhexlify(content)
|
||
decoded_content = hex_bytes.decode('utf-16-be', errors='replace')
|
||
print(f"替代解码成功: {decoded_content[:50]}...")
|
||
except Exception as alt_e:
|
||
print(f"替代解码失败: {str(alt_e)}")
|
||
# 如果所有解码方法都失败,发送原始内容
|
||
self.sms_received.emit(
|
||
sender,
|
||
timestamp,
|
||
f"[无法解码的消息] {content[:50]}..."
|
||
)
|
||
return
|
||
|
||
# 特殊格式处理 - 提取URL
|
||
url = None
|
||
if is_special_format:
|
||
# 尝试从特殊格式中提取URL
|
||
parts = content.split("003A", 1) # 003A是冒号的UCS2编码
|
||
if len(parts) > 1:
|
||
url_content = "003A" + parts[1] # 加回冒号
|
||
try:
|
||
url_text = ucs2_to_text(url_content)
|
||
url_match = re.search(r':(https?://[^\s]+)', url_text)
|
||
if url_match:
|
||
url = url_match.group(1)
|
||
print(f"提取URL: {url}")
|
||
except Exception as url_e:
|
||
print(f"URL提取错误: {str(url_e)}")
|
||
|
||
# 如果没有找到URL,尝试从普通文本中提取
|
||
if not url:
|
||
url_match = re.search(r'(https?://[^\s]+)', decoded_content)
|
||
if url_match:
|
||
url = url_match.group(1)
|
||
print(f"从普通文本提取URL: {url}")
|
||
|
||
# 创建或更新长短信记录
|
||
sms_id = f"{sender}_{timestamp[:10]}"
|
||
|
||
if sms_id not in self.concat_sms_parts:
|
||
prefix = "消息"
|
||
if ":" in decoded_content:
|
||
prefix = decoded_content.split(":", 1)[0].strip()
|
||
|
||
self.concat_sms_parts[sms_id] = {
|
||
'sender': sender,
|
||
'timestamp': timestamp,
|
||
'parts': [],
|
||
'urls': [],
|
||
'received_time': time.time(),
|
||
'prefix': prefix
|
||
}
|
||
|
||
# 添加这部分到长短信记录
|
||
if url and url not in self.concat_sms_parts[sms_id]['urls']:
|
||
self.concat_sms_parts[sms_id]['urls'].append(url)
|
||
|
||
if decoded_content not in self.concat_sms_parts[sms_id]['parts']:
|
||
self.concat_sms_parts[sms_id]['parts'].append(decoded_content)
|
||
|
||
# 更新接收时间
|
||
self.concat_sms_parts[sms_id]['received_time'] = time.time()
|
||
|
||
# 使用定时器,3秒后尝试合并长短信
|
||
threading.Timer(3.0, lambda: self._check_and_merge_sms(sms_id)).start()
|
||
|
||
print(f"已保存长短信部分,将在3秒后尝试合并")
|
||
|
||
except Exception as e:
|
||
print(f"处理长短信内容部分出错: {str(e)}")
|
||
# 出错时直接发送解码后的内容
|
||
try:
|
||
decoded = ucs2_to_text(content) if all(c in "0123456789ABCDEFabcdef" for c in content) else content
|
||
self.sms_received.emit(
|
||
sender,
|
||
timestamp,
|
||
decoded
|
||
)
|
||
except Exception as final_e:
|
||
print(f"最终解码尝试失败: {str(final_e)}")
|
||
self.sms_received.emit(
|
||
sender,
|
||
timestamp,
|
||
f"[消息解码失败] {content[:50]}..."
|
||
)
|
||
|
||
def _merge_sms_parts(self, sms_id):
|
||
"""合并长短信的所有部分"""
|
||
if sms_id not in self.concat_sms_parts:
|
||
return ""
|
||
|
||
sms_info = self.concat_sms_parts[sms_id]
|
||
|
||
# 如果只有一个部分,直接返回
|
||
if len(sms_info['parts']) == 1:
|
||
return sms_info['parts'][0]
|
||
|
||
# 合并所有部分
|
||
prefix = sms_info.get('prefix', '')
|
||
urls = sms_info.get('urls', [])
|
||
|
||
if urls:
|
||
# 如果有URL,优先使用URL格式返回
|
||
if prefix:
|
||
merged_content = f"{prefix}:\n" + "\n".join(urls)
|
||
else:
|
||
merged_content = "\n".join(urls)
|
||
else:
|
||
# 如果没有URL,合并所有文本部分
|
||
merged_content = "\n".join(sms_info['parts'])
|
||
|
||
return merged_content
|
||
|
||
def _cleanup_old_sms_parts(self):
|
||
"""清理超时的长短信部分"""
|
||
try:
|
||
current_time = time.time()
|
||
sms_ids_to_remove = []
|
||
|
||
for sms_id, sms_info in self.concat_sms_parts.items():
|
||
# 检查是否已处理且超过保留时间(10分钟)
|
||
if sms_info.get('is_processed', False) and current_time - sms_info.get('last_processed', 0) > 600:
|
||
sms_ids_to_remove.append(sms_id)
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 清理已处理的长短信: {sms_id}")
|
||
# 检查未处理但已超时的长短信(30秒)
|
||
elif not sms_info.get('is_processed', False) and current_time - sms_info.get('received_time', 0) > 30:
|
||
# 如果有内容但未处理(可能是因为只收到部分内容),尝试合并发送
|
||
if sms_info.get('parts', []):
|
||
try:
|
||
# 合并可用部分并发送
|
||
merged_content = self._merge_sms_parts(sms_id)
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 发送超时但未处理的长短信: {merged_content[:50]}...")
|
||
self.sms_received.emit(
|
||
sms_info['sender'],
|
||
sms_info['timestamp'],
|
||
f"[部分内容] {merged_content}"
|
||
)
|
||
except Exception as e:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 处理超时长短信时出错: {str(e)}")
|
||
|
||
sms_ids_to_remove.append(sms_id)
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 清理超时未处理的长短信: {sms_id}")
|
||
|
||
# 移除标记的记录
|
||
for sms_id in sms_ids_to_remove:
|
||
del self.concat_sms_parts[sms_id]
|
||
self.status_changed.emit(f"清理长短信记录: {sms_id}")
|
||
|
||
# 打印当前缓存状态
|
||
if self.concat_sms_parts:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 当前有 {len(self.concat_sms_parts)} 条长短信记录在缓存中")
|
||
except Exception as e:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 清理长短信部分时出错: {str(e)}")
|
||
|
||
def _decode_pdu_message(self, pdu_str):
|
||
"""Decode PDU format message (including Chinese characters)"""
|
||
try:
|
||
# Remove spaces and convert to bytes
|
||
pdu_str = pdu_str.replace(" ", "")
|
||
|
||
# 检查是否为长短信的一部分
|
||
if self._is_part_of_concatenated_sms(pdu_str):
|
||
# 可能是长短信的一部分,需要特殊处理
|
||
# 这里需要根据实际的PDU格式进行解析
|
||
pass
|
||
|
||
# Try to decode using our utility function
|
||
return ucs2_to_text(pdu_str)
|
||
except Exception as e:
|
||
print(f"PDU decode error: {str(e)}")
|
||
# If decoding fails, return the original string
|
||
return f"[Decode error: {pdu_str[:30]}...]"
|
||
|
||
def _is_part_of_concatenated_sms(self, content):
|
||
"""检查内容是否为长短信的一部分"""
|
||
try:
|
||
# 移除空格
|
||
content = content.replace(" ", "")
|
||
|
||
# 检查是否为UCS2编码
|
||
if not all(c in "0123456789ABCDEFabcdef" for c in content):
|
||
return False
|
||
|
||
# 检查内容长度是否足够
|
||
if len(content) < 10:
|
||
return False
|
||
|
||
# 检查是否包含特定模式
|
||
# 1. 检查特定前缀,这是已知的长短信特征
|
||
if content.startswith("62117ED94F6053D14E86957F6587672C"):
|
||
print(f"检测到长短信特定前缀: 62117ED94F6053D14E86957F6587672C")
|
||
return True
|
||
|
||
# 2. 检查内容是否包含URL的UCS2编码
|
||
# https的UCS2编码前缀: 00680074007400700073
|
||
if "00680074007400700073" in content:
|
||
print(f"检测到UCS2编码的HTTPS URL")
|
||
return True
|
||
|
||
# 3. 检查内容长度是否超过标准短信长度限制
|
||
# UCS2编码的短信最多支持70个字符,即140个字节,对应280个十六进制字符
|
||
if len(content) > 280:
|
||
print(f"内容长度({len(content)})超过标准短信限制")
|
||
return True
|
||
|
||
# 4. 尝试解码并检查是否包含特定内容标记
|
||
try:
|
||
decoded = ucs2_to_text(content)
|
||
# 检查解码后内容是否包含URL
|
||
if re.search(r'https?://', decoded):
|
||
print(f"检测到包含URL的内容")
|
||
return True
|
||
except:
|
||
pass
|
||
|
||
return False
|
||
except Exception as e:
|
||
print(f"检查长短信内容部分时出错: {str(e)}")
|
||
return False
|
||
|
||
def _initialize_module(self):
|
||
"""初始化LTE模块
|
||
|
||
设置基本配置,包括来电提醒、短信格式等
|
||
并获取设备信息
|
||
"""
|
||
try:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 初始化LTE模块")
|
||
|
||
# 每个命令间添加30毫秒延迟
|
||
|
||
# 检查并注销PCM音频,确保通话音频正确处理
|
||
self._stop_pcm_audio()
|
||
time.sleep(0.03) # 30毫秒延迟
|
||
|
||
# 检查当前CLIP状态
|
||
clip_status = self.send_at_command("AT+CLIP?")
|
||
time.sleep(0.03) # 30毫秒延迟
|
||
|
||
# 只有当CLIP不是1时才启用
|
||
if not clip_status or "+CLIP: 1" not in clip_status:
|
||
# 启用来电显示功能 (呼叫线路标识显示)
|
||
clip_response = self.send_at_command("AT+CLIP=1")
|
||
if "OK" in clip_response:
|
||
self.status_changed.emit("来电显示功能已启用")
|
||
else:
|
||
self.status_changed.emit("启用来电显示功能失败")
|
||
time.sleep(0.03) # 30毫秒延迟
|
||
|
||
# 检查当前SMS格式状态
|
||
sms_format = self.send_at_command("AT+CMGF?")
|
||
time.sleep(0.03) # 30毫秒延迟
|
||
|
||
# 只有当不是文本模式时才设置
|
||
if not sms_format or "+CMGF: 1" not in sms_format:
|
||
# 设置短信为文本模式
|
||
cmgf_response = self.send_at_command("AT+CMGF=1")
|
||
if "OK" in cmgf_response:
|
||
self.status_changed.emit("短信文本模式已启用")
|
||
else:
|
||
self.status_changed.emit("启用短信文本模式失败")
|
||
time.sleep(0.03) # 30毫秒延迟
|
||
|
||
# 检查新消息指示配置
|
||
cnmi_status = self.send_at_command("AT+CNMI?")
|
||
time.sleep(0.03) # 30毫秒延迟
|
||
|
||
# 只有当不是2,2,0,0,0时才设置
|
||
if not cnmi_status or "+CNMI: 2,2,0,0,0" not in cnmi_status:
|
||
# 设置新消息指示
|
||
cnmi_response = self.send_at_command("AT+CNMI=2,2,0,0,0")
|
||
if "OK" in cnmi_response:
|
||
self.status_changed.emit("短信通知已启用")
|
||
else:
|
||
self.status_changed.emit("启用短信通知失败")
|
||
time.sleep(0.03) # 30毫秒延迟
|
||
|
||
# 获取模块信息 (厂商、型号、IMEI等)
|
||
self._get_module_info()
|
||
time.sleep(0.03) # 30毫秒延迟
|
||
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - LTE模块初始化完成")
|
||
|
||
except Exception as e:
|
||
self.status_changed.emit(f"初始化模块失败: {str(e)}")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 初始化模块失败: {str(e)}")
|
||
|
||
def _get_module_info(self):
|
||
"""获取模块信息(初始化时调用一次)"""
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 获取设备基本信息")
|
||
|
||
# 记录上次更新时间
|
||
self.last_info_update = time.time()
|
||
|
||
# 获取制造商信息
|
||
response = self.send_at_command("AT+CGMI")
|
||
if response and "OK" in response:
|
||
# 移除命令回显和OK响应,只保留实际内容
|
||
lines = [line.strip() for line in response.split('\n') if line.strip()]
|
||
# 过滤掉AT命令回显和OK响应
|
||
content_lines = [line for line in lines if line != "OK" and not line.startswith("AT+")]
|
||
self.manufacturer = '\n'.join(content_lines).strip()
|
||
|
||
# 获取模块型号
|
||
response = self.send_at_command("AT+CGMM")
|
||
if response and "OK" in response:
|
||
# 移除命令回显和OK响应,只保留实际内容
|
||
lines = [line.strip() for line in response.split('\n') if line.strip()]
|
||
# 过滤掉AT命令回显和OK响应
|
||
content_lines = [line for line in lines if line != "OK" and not line.startswith("AT+")]
|
||
self.model = '\n'.join(content_lines).strip()
|
||
|
||
# 获取IMEI号码
|
||
response = self.send_at_command("AT+CGSN")
|
||
if response and "OK" in response:
|
||
# 移除命令回显和OK响应,只保留实际内容
|
||
lines = [line.strip() for line in response.split('\n') if line.strip()]
|
||
# 过滤掉AT命令回显和OK响应
|
||
content_lines = [line for line in lines if line != "OK" and not line.startswith("AT+")]
|
||
self.imei = '\n'.join(content_lines).strip()
|
||
|
||
# 获取固件版本
|
||
response = self.send_at_command("AT+CGMR")
|
||
if response and "OK" in response:
|
||
match = re.search(r'\+CGMR: (.+)', response)
|
||
if match:
|
||
self.firmware = match.group(1)
|
||
else:
|
||
# 移除命令回显和OK响应,只保留实际内容
|
||
lines = [line.strip() for line in response.split('\n') if line.strip()]
|
||
# 过滤掉AT命令回显和OK响应
|
||
content_lines = [line for line in lines if line != "OK" and not line.startswith("AT+")]
|
||
self.firmware = '\n'.join(content_lines).strip()
|
||
|
||
# 获取电话号码、运营商和信号强度信息
|
||
self._update_phone_number()
|
||
self._update_carrier_info()
|
||
self._update_signal_strength()
|
||
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 设备基本信息获取完成")
|
||
|
||
def _update_phone_number(self):
|
||
"""更新电话号码信息(缓存30分钟)"""
|
||
# 添加缓存检查,减少AT命令交互
|
||
current_time = time.time()
|
||
if hasattr(self, 'last_phone_update') and current_time - self.last_phone_update < 1800: # 30分钟缓存
|
||
# 使用缓存的值
|
||
return self.phone_number
|
||
|
||
response = self.send_at_command("AT+CNUM")
|
||
if response and "+CNUM:" in response:
|
||
match = re.search(r'\+CNUM: "[^"]*","([^"]+)"', response)
|
||
if match:
|
||
self.phone_number = match.group(1)
|
||
self.last_phone_update = current_time # 记录更新时间
|
||
return self.phone_number
|
||
|
||
# 如果没有找到号码但有之前的值,保留之前的值
|
||
return self.phone_number
|
||
|
||
def _update_carrier_info(self):
|
||
"""更新运营商信息(缓存10分钟)"""
|
||
# 添加缓存检查,减少AT命令交互
|
||
current_time = time.time()
|
||
if hasattr(self, 'last_carrier_update') and current_time - self.last_carrier_update < 600: # 10分钟缓存
|
||
# 使用缓存的值
|
||
return (self.carrier, self.network_type)
|
||
|
||
# 更新运营商信息
|
||
carrier_updated = False
|
||
response = self.send_at_command("AT+COPS?")
|
||
if response and "+COPS:" in response:
|
||
match = re.search(r'\+COPS: \d+,\d+,"([^"]+)"', response)
|
||
if match:
|
||
self.carrier = match.group(1)
|
||
carrier_updated = True
|
||
|
||
# 更新网络类型
|
||
network_updated = False
|
||
response = self.send_at_command("AT+CPSI?")
|
||
if response and "+CPSI:" in response:
|
||
# 移除命令回显,只保留+CPSI:部分
|
||
match = re.search(r'\+CPSI:(.+)', response)
|
||
if match:
|
||
parts = match.group(1).split(',')
|
||
if len(parts) > 1:
|
||
self.network_type = parts[0].strip()
|
||
network_updated = True
|
||
else:
|
||
parts = response.split(',')
|
||
if len(parts) > 1:
|
||
self.network_type = parts[0].replace("+CPSI:", "").strip()
|
||
network_updated = True
|
||
|
||
# 如果有更新,记录更新时间
|
||
if carrier_updated or network_updated:
|
||
self.last_carrier_update = current_time
|
||
|
||
return (self.carrier, self.network_type)
|
||
|
||
def _update_signal_strength(self):
|
||
"""更新信号强度信息(无缓存,需要实时监控)"""
|
||
response = self.send_at_command("AT+CSQ")
|
||
if response and "+CSQ:" in response:
|
||
match = re.search(r'\+CSQ: (\d+),', response)
|
||
if match:
|
||
rssi = int(match.group(1))
|
||
if rssi == 99:
|
||
self.signal_strength = "Unknown"
|
||
else:
|
||
# Convert to dBm (-113 to -51 dBm)
|
||
dbm = -113 + (2 * rssi)
|
||
self.signal_strength = f"{dbm} dBm ({rssi}/31)"
|
||
|
||
return self.signal_strength
|
||
|
||
def get_carrier_info(self):
|
||
"""获取运营商信息(使用缓存机制)"""
|
||
if not self.connected:
|
||
return None
|
||
|
||
# 使用缓存的值,不每次都发送AT命令
|
||
if hasattr(self, 'carrier') and self.carrier:
|
||
# 只有在超过缓存时间时才更新
|
||
current_time = time.time()
|
||
if not hasattr(self, 'last_carrier_update') or current_time - self.last_carrier_update >= 600: # 10分钟缓存
|
||
self._update_carrier_info()
|
||
else:
|
||
# 第一次请求,直接更新
|
||
self._update_carrier_info()
|
||
|
||
return self.carrier
|
||
|
||
def get_phone_number(self):
|
||
"""获取电话号码(使用缓存机制)"""
|
||
if not self.connected:
|
||
return None
|
||
|
||
# 使用缓存的值,不每次都发送AT命令
|
||
if hasattr(self, 'phone_number') and self.phone_number:
|
||
# 只有在超过缓存时间时才更新
|
||
current_time = time.time()
|
||
if not hasattr(self, 'last_phone_update') or current_time - self.last_phone_update >= 1800: # 30分钟缓存
|
||
self._update_phone_number()
|
||
else:
|
||
# 第一次请求,直接更新
|
||
self._update_phone_number()
|
||
|
||
return self.phone_number
|
||
|
||
def get_network_info(self):
|
||
"""获取网络信息(使用缓存机制)"""
|
||
if not self.connected:
|
||
return None
|
||
|
||
# 使用缓存的值,不每次都发送AT命令
|
||
if hasattr(self, 'network_type') and self.network_type:
|
||
# 只有在超过缓存时间时才更新(与运营商信息共享缓存时间)
|
||
current_time = time.time()
|
||
if not hasattr(self, 'last_carrier_update') or current_time - self.last_carrier_update >= 600: # 10分钟缓存
|
||
self._update_carrier_info() # 这里会同时更新network_type
|
||
else:
|
||
# 第一次请求,直接更新
|
||
self._update_carrier_info()
|
||
|
||
return self.network_type
|
||
|
||
def get_signal_strength(self):
|
||
"""获取信号强度(实时更新)"""
|
||
if not self.connected:
|
||
return None
|
||
|
||
# 信号强度需要实时更新,其他信息则使用缓存
|
||
self._update_signal_strength()
|
||
return self.signal_strength
|
||
|
||
def get_module_info(self):
|
||
"""获取模块信息(使用缓存机制)"""
|
||
if not self.connected:
|
||
return {}
|
||
|
||
# 检查是否需要刷新模块信息(默认每小时更新一次)
|
||
current_time = time.time()
|
||
if not hasattr(self, 'last_info_update') or current_time - self.last_info_update >= 3600: # 1小时缓存
|
||
self._get_module_info()
|
||
else:
|
||
# 仅更新可能变化的信息:信号强度
|
||
self._update_signal_strength()
|
||
|
||
# 适当更新运营商信息(如果缓存过期)
|
||
if not hasattr(self, 'last_carrier_update') or current_time - self.last_carrier_update >= 600:
|
||
self._update_carrier_info()
|
||
|
||
return {
|
||
'manufacturer': self.manufacturer,
|
||
'model': self.model,
|
||
'imei': self.imei,
|
||
'firmware': self.firmware,
|
||
'phone_number': self.phone_number,
|
||
'carrier': self.carrier,
|
||
'network_type': self.network_type,
|
||
'signal_strength': self.signal_strength
|
||
}
|
||
|
||
def _fetch_sms(self, storage, index):
|
||
"""Fetch SMS content by index"""
|
||
response = self.send_at_command(f'AT+CMGR={index}')
|
||
if response and "+CMGR:" in response:
|
||
# Parse SMS header
|
||
header_match = re.search(r'\+CMGR: "[^"]*","([^"]*)",[^,]*,"([^"]*)"', response)
|
||
if header_match:
|
||
sender = header_match.group(1)
|
||
timestamp = header_match.group(2)
|
||
|
||
# Check if sender is in UCS2 format (starts with 00)
|
||
if sender.startswith("00"):
|
||
try:
|
||
sender = ucs2_to_text(sender)
|
||
except:
|
||
pass # Keep original if decoding fails
|
||
|
||
# Extract message content
|
||
lines = response.split('\n')
|
||
message = ""
|
||
|
||
if len(lines) > 1:
|
||
content_line = lines[1].strip()
|
||
message = content_line
|
||
|
||
# Check if the content is in UCS2 format (hex string)
|
||
if all(c in "0123456789ABCDEFabcdef" for c in content_line.replace(" ", "")):
|
||
try:
|
||
# Try to decode as UCS2
|
||
message = ucs2_to_text(content_line)
|
||
self.status_changed.emit("Decoded UCS2 message from storage")
|
||
except Exception as e:
|
||
self.status_changed.emit(f"Failed to decode UCS2 from storage: {str(e)}")
|
||
# Keep original if decoding fails
|
||
|
||
self.sms_received.emit(sender, timestamp, message)
|
||
|
||
def _register_pcm_audio(self):
|
||
"""注册PCM音频(用于通话开始时)
|
||
按照文档要求,在VOICE CALL: BEGIN后执行AT+CPCMREG=1
|
||
"""
|
||
if not self.connected or not self.at_serial:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注册失败:未连接")
|
||
return False
|
||
|
||
# 如果已经不在通话中了,跳过注册
|
||
if not self.in_call:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 不在通话中,跳过PCM音频注册")
|
||
self.status_changed.emit("Not in call, PCM audio registration skipped")
|
||
return False
|
||
|
||
try:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 开始PCM音频注册过程")
|
||
|
||
# 清除可能的额外数据
|
||
if self.at_serial.in_waiting > 0:
|
||
self.at_serial.read(self.at_serial.in_waiting)
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 清除了缓冲区数据")
|
||
|
||
# 设置PCM格式为8K采样率(如需要16K,可更改为AT+CPCMFRM=1)
|
||
try:
|
||
with self.lock:
|
||
self.at_serial.write(b'AT+CPCMFRM=0\r')
|
||
time.sleep(0.1)
|
||
if self.at_serial.in_waiting > 0:
|
||
resp = self.at_serial.read(self.at_serial.in_waiting).decode('utf-8', errors='ignore')
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM格式设置响应: {resp}")
|
||
except Exception as e:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 设置PCM格式出错: {str(e)}")
|
||
|
||
# 直接发送PCM音频注册命令,使用更短的超时
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 发送PCM音频注册命令")
|
||
|
||
# 确保没有另一个命令在发送
|
||
with self.lock:
|
||
self.at_serial.write(b'AT+CPCMREG=1\r')
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注册命令已发送")
|
||
|
||
# 等待较短的时间以获取响应
|
||
time.sleep(0.1)
|
||
|
||
# 试图读取响应
|
||
response = ""
|
||
start_time = time.time()
|
||
while time.time() - start_time < 0.5: # 最多等待0.5秒
|
||
if self.at_serial.in_waiting > 0:
|
||
response += self.at_serial.read(self.at_serial.in_waiting).decode('utf-8', errors='ignore')
|
||
if "OK" in response or "ERROR" in response:
|
||
break
|
||
time.sleep(0.05)
|
||
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注册响应: {response}")
|
||
|
||
# 记录是否成功
|
||
success = "OK" in response
|
||
|
||
# 根据响应结果发送状态更新
|
||
if success:
|
||
self.status_changed.emit("PCM audio registered successfully")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注册成功")
|
||
else:
|
||
self.status_changed.emit("PCM audio registration sent")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注册状态未知")
|
||
|
||
# 无论响应如何,发送激活信号,系统将尝试处理音频
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 发送PCM音频激活信号")
|
||
self.pcm_audio_status.emit(True)
|
||
|
||
# 添加调试记录
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注册流程完成")
|
||
return True
|
||
|
||
except Exception as e:
|
||
self.status_changed.emit(f"PCM audio registration error: {str(e)}")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注册出错: {str(e)}")
|
||
|
||
# 错误发生时,仍然尝试激活音频,保持一致行为
|
||
self.pcm_audio_status.emit(True)
|
||
return False
|
||
|
||
def _unregister_pcm_audio(self):
|
||
"""取消注册PCM音频(用于通话结束时)
|
||
按照文档要求,在VOICE CALL: END后执行AT+CPCMREG=0
|
||
"""
|
||
if not self.connected or not self.at_serial:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 取消PCM音频注册失败:未连接")
|
||
# 即使未连接,也发送停止信号
|
||
self.pcm_audio_status.emit(False)
|
||
return False
|
||
|
||
try:
|
||
# 确保没有另一个命令在发送
|
||
with self.lock:
|
||
# 清除任何待处理的数据
|
||
if self.at_serial.in_waiting > 0:
|
||
self.at_serial.read(self.at_serial.in_waiting)
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 清除了PCM音频注销前的缓冲区数据")
|
||
|
||
# 使用直接写入代替send_at_command,避免阻塞
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 发送PCM音频注销命令")
|
||
self.at_serial.write(b'AT+CPCMREG=0\r')
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注销命令已发送")
|
||
|
||
# 给一点时间让模块响应
|
||
time.sleep(0.1)
|
||
|
||
# 尝试读取响应,但不等待过长时间
|
||
response = ""
|
||
start_time = time.time()
|
||
while time.time() - start_time < 0.3: # 等待最多0.3秒
|
||
if self.at_serial.in_waiting > 0:
|
||
response += self.at_serial.read(self.at_serial.in_waiting).decode('utf-8', errors='ignore')
|
||
if "OK" in response:
|
||
break
|
||
time.sleep(0.05)
|
||
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注销响应: {response}")
|
||
success = "OK" in response
|
||
|
||
# 根据响应结果更新状态
|
||
if success:
|
||
self.status_changed.emit("PCM audio unregistered successfully")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注销成功")
|
||
else:
|
||
self.status_changed.emit("PCM audio unregistration sent")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注销状态未知")
|
||
|
||
# 无论命令是否成功,都发送停止信号
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 发送PCM音频停止信号")
|
||
self.pcm_audio_status.emit(False)
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
self.status_changed.emit(f"PCM audio unregistration error: {str(e)}")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注销错误: {str(e)}")
|
||
|
||
# 出错时也发送停止信号
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 注销出错,但仍发送停止信号")
|
||
self.pcm_audio_status.emit(False)
|
||
return False
|
||
|
||
def _ensure_pcm_audio_unregistered(self):
|
||
"""确保PCM音频被取消注册"""
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 确保PCM音频已注销")
|
||
|
||
# 首先确保通话状态正确
|
||
self.in_call = False # 强制设置为非通话状态,确保在所有情况下状态一致
|
||
|
||
# 直接取消注册PCM音频
|
||
result = self._unregister_pcm_audio()
|
||
if result:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注销成功完成")
|
||
else:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注销可能未完成,但已发送停止信号")
|
||
|
||
# 返回实际的操作结果,以便调用者可以适当处理
|
||
return result
|
||
|
||
def make_call(self, number):
|
||
"""Make a phone call (MO - Mobile Originated call)"""
|
||
if not self.connected:
|
||
return False
|
||
|
||
# 如果已经在通话中,先结束当前通话
|
||
if self.in_call:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 已在通话中,先结束当前通话")
|
||
self.end_call()
|
||
|
||
# 使用循环检查通话状态,而不是固定等待时间
|
||
wait_start = time.time()
|
||
while self.in_call and time.time() - wait_start < 3.0: # 最多等待3秒
|
||
time.sleep(0.1)
|
||
|
||
if self.in_call:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 无法结束先前通话,放弃拨号")
|
||
self.status_changed.emit("Failed to end previous call")
|
||
return False
|
||
|
||
# 确保PCM音频已经关闭
|
||
self._ensure_pcm_audio_unregistered()
|
||
|
||
# 设置模块为语音模式
|
||
try:
|
||
self.send_at_command("AT+FCLASS=8") # 设置为语音模式,确保正确处理语音呼叫
|
||
except Exception as e:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 设置语音模式出错: {str(e)}")
|
||
|
||
# 发起拨号命令
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 发起拨打电话到 {number}")
|
||
response = self.send_at_command(f"ATD{number};")
|
||
|
||
if "OK" in response:
|
||
self.call_number = number
|
||
self.status_changed.emit(f"Calling {number}")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 正在拨打 {number}")
|
||
|
||
# 注意:设置in_call=True应该在收到VOICE CALL: BEGIN之后
|
||
# 这里只记录目标号码,不立即设置呼叫状态
|
||
# 在收到VOICE CALL: BEGIN事件后,会自动设置in_call=True并注册PCM音频
|
||
|
||
return True
|
||
else:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 拨打电话失败: {response}")
|
||
self.status_changed.emit(f"Failed to call {number}")
|
||
return False
|
||
|
||
def answer_call(self):
|
||
"""接听来电"""
|
||
try:
|
||
# 首先检查是否真的有来电
|
||
calls = self.get_call_status()
|
||
has_incoming_call = False
|
||
|
||
for call in calls:
|
||
if call.get('stat') == 4 and call.get('dir') == 1: # 来电中(MT)
|
||
has_incoming_call = True
|
||
break
|
||
|
||
if not has_incoming_call:
|
||
self.status_changed.emit("当前没有待接听的来电")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 尝试接听来电失败:当前无待接听来电")
|
||
return False
|
||
|
||
# 停止所有铃声
|
||
self._stop_all_ringtones()
|
||
time.sleep(0.03) # 30毫秒延迟
|
||
|
||
# 确保PCM音频已注册
|
||
self._ensure_pcm_audio_registered()
|
||
time.sleep(0.03) # 30毫秒延迟
|
||
|
||
# 发送接听命令
|
||
response = self.send_at_command("ATA")
|
||
|
||
if "OK" in response or "CONNECT" in response:
|
||
# 标记已接通
|
||
self.call_connected = True
|
||
self.in_call = True
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 通话已接通")
|
||
return True
|
||
else:
|
||
# 即使命令返回失败,仍检查通话是否已建立(有时模块会接通但返回错误)
|
||
time.sleep(0.5) # 等待一小段时间让通话建立
|
||
|
||
# 再次检查通话状态,确认是否已接通
|
||
calls_after = self.get_call_status()
|
||
call_established = False
|
||
|
||
for call in calls_after:
|
||
if call.get('stat') in [0, 1] and call.get('dir') == 1: # 活动或保持状态的呼入通话
|
||
call_established = True
|
||
self.in_call = True
|
||
self.call_connected = True
|
||
break
|
||
|
||
if call_established:
|
||
self.status_changed.emit("通话已接通")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 通话接听成功")
|
||
return True
|
||
else:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 接听失败: {response}")
|
||
return False
|
||
|
||
except Exception as e:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 接听电话时出错: {str(e)}")
|
||
return False
|
||
|
||
def end_call(self):
|
||
"""结束当前通话,根据通话状态使用不同的挂断命令"""
|
||
if not self.connected:
|
||
return False
|
||
|
||
# 获取当前通话状态
|
||
calls = self.get_call_status()
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 当前通话状态: {calls}")
|
||
|
||
# 根据通话状态选择合适的挂断命令
|
||
if not calls:
|
||
# 没有活动通话,但为安全起见仍发送挂断命令
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 无活动通话,但仍发送挂断命令")
|
||
response = self.send_at_command("ATH")
|
||
|
||
# 检查响应中是否包含OK
|
||
if "OK" in response:
|
||
self.in_call = False
|
||
self.call_connected = False
|
||
self.status_changed.emit("通话结束")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 通话已结束")
|
||
|
||
# 通话结束后,立即取消PCM音频注册
|
||
self._ensure_pcm_audio_unregistered()
|
||
return True
|
||
else:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 挂断通话失败,响应: {response}")
|
||
return False
|
||
else:
|
||
# 检查第一个通话的状态
|
||
call = calls[0]
|
||
stat = call.get('stat', -1)
|
||
|
||
if stat == 4: # 来电中(MT)
|
||
# 来电振铃状态,使用 AT+CHUP 命令挂断
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 使用AT+CHUP挂断未接通的来电")
|
||
response = self.send_at_command("AT+CHUP")
|
||
|
||
# 对于AT+CHUP命令,检查特殊成功标志
|
||
if "OK" in response or "NO CARRIER" in response or "VOICE CALL: END" in response:
|
||
self.in_call = False
|
||
self.call_connected = False
|
||
self.status_changed.emit("来电已拒绝")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 来电已拒绝")
|
||
|
||
# 通话结束后,立即取消PCM音频注册
|
||
self._ensure_pcm_audio_unregistered()
|
||
return True
|
||
|
||
# 检查响应中是否包含+CLCC行,且状态为6(终止)
|
||
# 如: +CLCC: 1,1,6,0,0,"18571797477",129,""
|
||
if "+CLCC:" in response and ",6," in response:
|
||
self.in_call = False
|
||
self.call_connected = False
|
||
self.status_changed.emit("来电已拒绝")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 通过CLCC状态确认来电已拒绝")
|
||
|
||
# 通话结束后,立即取消PCM音频注册
|
||
self._ensure_pcm_audio_unregistered()
|
||
return True
|
||
|
||
# 发送CHUP后立即检查通话状态
|
||
time.sleep(0.5) # 短暂延迟,等待模块处理
|
||
after_calls = self.get_call_status()
|
||
if not after_calls:
|
||
self.in_call = False
|
||
self.call_connected = False
|
||
self.status_changed.emit("来电已拒绝")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 二次确认来电已拒绝")
|
||
|
||
# 通话结束后,立即取消PCM音频注册
|
||
self._ensure_pcm_audio_unregistered()
|
||
return True
|
||
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 拒绝来电失败,响应: {response}")
|
||
return False
|
||
else:
|
||
# 其他状态使用 ATH 命令挂断
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 使用ATH挂断通话,状态: {self.call_states.get(stat, '未知')}")
|
||
response = self.send_at_command("ATH")
|
||
|
||
# 检查响应中是否包含OK或其他成功标志
|
||
if "OK" in response or "NO CARRIER" in response or "VOICE CALL: END" in response:
|
||
self.in_call = False
|
||
self.call_connected = False
|
||
self.status_changed.emit("通话结束")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 通话已结束")
|
||
|
||
# 通话结束后,立即取消PCM音频注册
|
||
self._ensure_pcm_audio_unregistered()
|
||
return True
|
||
|
||
# 发送ATH后立即检查通话状态
|
||
time.sleep(0.5) # 短暂延迟,等待模块处理
|
||
after_calls = self.get_call_status()
|
||
if not after_calls:
|
||
self.in_call = False
|
||
self.call_connected = False
|
||
self.status_changed.emit("通话结束")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 二次确认通话已结束")
|
||
|
||
# 通话结束后,立即取消PCM音频注册
|
||
self._ensure_pcm_audio_unregistered()
|
||
return True
|
||
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 挂断通话失败,响应: {response}")
|
||
return False
|
||
|
||
def send_sms(self, number, message):
|
||
"""Send SMS message"""
|
||
if not self.connected:
|
||
return False
|
||
|
||
# Format the phone number
|
||
formatted_number = format_phone_number(number)
|
||
|
||
# Clear any pending responses
|
||
while not self.response_queue.empty():
|
||
self.response_queue.get()
|
||
|
||
# Set text mode and wait for OK response
|
||
response = self.send_at_command("AT+CMGF=1")
|
||
if "OK" not in response:
|
||
self.status_changed.emit("Failed to set SMS text mode")
|
||
return False
|
||
|
||
# Add debug message
|
||
self.status_changed.emit(f"Sending SMS to {formatted_number}")
|
||
|
||
try:
|
||
# Check if message contains Chinese characters
|
||
if is_chinese_text(message):
|
||
# Set character set to UCS2 for Unicode support
|
||
response = self.send_at_command('AT+CSCS="UCS2"')
|
||
if "OK" not in response:
|
||
self.status_changed.emit("Failed to set UCS2 character set")
|
||
return False
|
||
|
||
# Convert message to UCS2 hex string
|
||
hex_message = text_to_ucs2(message)
|
||
if not hex_message:
|
||
self.status_changed.emit("Failed to encode message")
|
||
return False
|
||
|
||
# Convert phone number to UCS2 format
|
||
hex_number = text_to_ucs2(formatted_number)
|
||
if not hex_number:
|
||
self.status_changed.emit("Failed to encode phone number")
|
||
return False
|
||
|
||
# Send message command with UCS2 encoded phone number
|
||
cmd = f'AT+CMGS="{hex_number}"'
|
||
self.at_serial.write((cmd + '\r').encode())
|
||
time.sleep(0.5) # Wait for > prompt
|
||
|
||
# Send message content and Ctrl+Z to end
|
||
self.at_serial.write(hex_message.encode() + b'\x1A')
|
||
self.status_changed.emit("Sending UCS2 encoded message...")
|
||
else:
|
||
# Set character set to GSM for ASCII support
|
||
response = self.send_at_command('AT+CSCS="GSM"')
|
||
if "OK" not in response:
|
||
self.status_changed.emit("Failed to set GSM character set")
|
||
return False
|
||
|
||
# Send message command
|
||
cmd = f'AT+CMGS="{formatted_number}"'
|
||
self.at_serial.write((cmd + '\r').encode())
|
||
time.sleep(0.5) # Wait for > prompt
|
||
|
||
# Send message content and Ctrl+Z to end
|
||
self.at_serial.write(message.encode() + b'\x1A')
|
||
self.status_changed.emit("Sending ASCII message...")
|
||
|
||
# Wait for response with longer timeout
|
||
start_time = time.time()
|
||
response = []
|
||
|
||
while time.time() - start_time < 15.0: # Increased timeout to 15 seconds
|
||
try:
|
||
line = self.response_queue.get(timeout=0.5)
|
||
response.append(line)
|
||
self.status_changed.emit(f"SMS response: {line}")
|
||
|
||
if "+CMGS:" in line:
|
||
self.status_changed.emit(f"SMS sent to {formatted_number}")
|
||
return True
|
||
elif "ERROR" in line or "+CMS ERROR:" in line:
|
||
self.status_changed.emit(f"SMS error: {line}")
|
||
return False
|
||
except queue.Empty:
|
||
continue
|
||
|
||
# If we get here, we timed out waiting for a response
|
||
self.status_changed.emit(f"SMS send timeout. Last response: {response[-1] if response else 'None'}")
|
||
return False
|
||
|
||
except Exception as e:
|
||
self.status_changed.emit(f"SMS send exception: {str(e)}")
|
||
return False
|
||
|
||
def delete_sms(self, index=None, delete_type=None):
|
||
"""Delete SMS messages
|
||
|
||
delete_type:
|
||
0 - Delete message at index
|
||
1 - Delete all read messages
|
||
2 - Delete all read and sent messages
|
||
3 - Delete all read, sent and unsent messages
|
||
4 - Delete all messages
|
||
"""
|
||
if not self.connected:
|
||
return False
|
||
|
||
if index is not None and delete_type is not None:
|
||
command = f"AT+CMGD={index},{delete_type}"
|
||
elif index is not None:
|
||
command = f"AT+CMGD={index}"
|
||
elif delete_type is not None:
|
||
command = f"AT+CMGD=1,{delete_type}"
|
||
else:
|
||
return False
|
||
|
||
response = self.send_at_command(command)
|
||
if "OK" in response:
|
||
self.status_changed.emit("SMS deleted")
|
||
return True
|
||
return False
|
||
|
||
def get_sms_list(self, status="ALL"):
|
||
"""Get list of SMS messages
|
||
|
||
status:
|
||
"REC UNREAD" - Unread messages
|
||
"REC READ" - Read messages
|
||
"STO UNSENT" - Stored unsent messages
|
||
"STO SENT" - Stored sent messages
|
||
"ALL" - All messages
|
||
"""
|
||
if not self.connected:
|
||
return []
|
||
|
||
# Set text mode
|
||
self.send_at_command("AT+CMGF=1")
|
||
|
||
# Get messages
|
||
if status == "ALL":
|
||
response = self.send_at_command("AT+CMGL")
|
||
else:
|
||
response = self.send_at_command(f'AT+CMGL="{status}"')
|
||
|
||
if not response or "OK" not in response:
|
||
return []
|
||
|
||
messages = []
|
||
lines = response.split('\n')
|
||
i = 0
|
||
|
||
while i < len(lines):
|
||
line = lines[i]
|
||
if line.startswith("+CMGL:"):
|
||
# Parse header
|
||
header_match = re.search(r'\+CMGL: (\d+),"([^"]*)",'
|
||
r'"([^"]*)",[^,]*,"([^"]*)"', line)
|
||
if header_match:
|
||
index = header_match.group(1)
|
||
msg_status = header_match.group(2)
|
||
sender = header_match.group(3)
|
||
timestamp = header_match.group(4)
|
||
|
||
# Get message content
|
||
if i + 1 < len(lines):
|
||
content = lines[i + 1]
|
||
|
||
# Check if PDU or text mode
|
||
if any(c for c in content if not (c.isalnum() or c.isspace() or c in '+-,.;:!?')):
|
||
# Likely PDU data, decode it
|
||
content = self._decode_pdu_message(content)
|
||
|
||
messages.append({
|
||
'index': index,
|
||
'status': msg_status,
|
||
'sender': sender,
|
||
'timestamp': timestamp,
|
||
'content': content
|
||
})
|
||
|
||
i += 2 # Skip content line
|
||
else:
|
||
i += 1
|
||
else:
|
||
i += 1
|
||
else:
|
||
i += 1
|
||
|
||
return messages
|
||
|
||
def get_call_status(self):
|
||
"""
|
||
获取当前所有通话状态
|
||
|
||
返回值为通话列表,每个通话包含:
|
||
- id: 通话ID
|
||
- dir: 方向(0=MO, 1=MT)
|
||
- stat: 状态(0=活动, 1=保持, 2=拨号, 3=振铃, 4=来电, 5=等待)
|
||
- mode: 模式(0=语音)
|
||
- mpty: 多方通话(0=非多方通话)
|
||
- number: 电话号码
|
||
"""
|
||
if not self.is_connected():
|
||
return []
|
||
|
||
# 检查是否有缓存且在短时间内(500毫秒内)
|
||
current_time = time.time()
|
||
if hasattr(self, 'last_call_status_check') and hasattr(self, 'cached_call_status'):
|
||
if current_time - self.last_call_status_check < 0.5: # 500毫秒内直接使用缓存结果
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 使用缓存的通话状态 ({int((current_time - self.last_call_status_check) * 1000)}ms)")
|
||
return self.cached_call_status
|
||
|
||
try:
|
||
# 记录本次查询时间
|
||
self.last_call_status_check = current_time
|
||
|
||
# 发送AT+CLCC查询通话状态命令
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 发送AT+CLCC查询通话状态")
|
||
response = self.send_at_command("AT+CLCC")
|
||
calls = []
|
||
|
||
# 检查响应是否有效
|
||
if not response:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - AT+CLCC无响应")
|
||
self.cached_call_status = []
|
||
return []
|
||
|
||
# 检查是否有错误响应
|
||
if "ERROR" in response:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - AT+CLCC返回错误: {response}")
|
||
self.cached_call_status = []
|
||
return []
|
||
|
||
# 检查响应中是否只有OK(无通话)
|
||
if "+CLCC:" not in response:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 无活动通话")
|
||
self.cached_call_status = []
|
||
return []
|
||
|
||
# 解析响应
|
||
lines = response.strip().split('\n')
|
||
for line in lines:
|
||
line = line.strip()
|
||
if not line.startswith('+CLCC:'):
|
||
continue
|
||
|
||
try:
|
||
# +CLCC: <id>,<dir>,<stat>,<mode>,<mpty>[,<number>,<type>[,<alpha>]]
|
||
parts = line[7:].strip().split(',')
|
||
|
||
# 验证是否有足够的字段
|
||
if len(parts) < 5:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - CLCC响应格式不完整: {line}")
|
||
continue
|
||
|
||
# 尝试解析通话信息
|
||
call = {
|
||
'id': int(parts[0].strip()),
|
||
'dir': int(parts[1].strip()),
|
||
'stat': int(parts[2].strip()),
|
||
'mode': int(parts[3].strip()),
|
||
'mpty': int(parts[4].strip())
|
||
}
|
||
|
||
# 判断是否有电话号码字段
|
||
if len(parts) > 5:
|
||
number = parts[5].strip()
|
||
if number.startswith('"') and number.endswith('"'):
|
||
number = number[1:-1] # 移除引号
|
||
call['number'] = number
|
||
|
||
# 记录该通话状态的文本描述(用于日志)
|
||
state_text = self.call_states.get(call['stat'], "未知状态")
|
||
direction = "呼出" if call['dir'] == 0 else "呼入"
|
||
number_info = f", 号码: {call.get('number', '未知')}" if 'number' in call else ""
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 检测到{direction}通话: {state_text}{number_info}")
|
||
|
||
calls.append(call)
|
||
except Exception as parse_error:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 解析CLCC响应行错误: {str(parse_error)}, 行: {line}")
|
||
continue
|
||
|
||
# 保存缓存结果
|
||
self.cached_call_status = calls
|
||
|
||
# 输出通话状态摘要
|
||
if calls:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 当前有 {len(calls)} 个活动通话")
|
||
else:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 没有活动通话")
|
||
|
||
# 通话状态变化时的特殊处理
|
||
if calls and not self.in_call:
|
||
# 之前不在通话,现在有通话 - 进入通话状态
|
||
self.in_call = True
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 检测到新通话,共 {len(calls)} 个通话")
|
||
|
||
# 获取最高优先级的通话状态
|
||
highest_priority_call = None
|
||
for call in calls:
|
||
# 优先级: 活动 > 来电 > 拨号 > 其他
|
||
if highest_priority_call is None or call['stat'] < highest_priority_call['stat']:
|
||
highest_priority_call = call
|
||
|
||
# 记录主叫号码或被叫号码
|
||
if highest_priority_call and 'number' in highest_priority_call:
|
||
self.call_number = highest_priority_call['number']
|
||
else:
|
||
self.call_number = ""
|
||
|
||
# 如果是状态为0的通话(活动通话),则更新通话已接通标志和时间
|
||
if highest_priority_call and highest_priority_call['stat'] == 0:
|
||
self.call_connected = True
|
||
self.call_connect_time = time.time()
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 通话已接通,记录开始时间")
|
||
|
||
elif not calls and self.in_call:
|
||
# 之前在通话,现在没有通话 - 退出通话状态
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 所有通话已结束")
|
||
self.in_call = False
|
||
|
||
# 备份通话状态,然后清除
|
||
was_connected = self.call_connected
|
||
self.call_connected = False
|
||
|
||
# 如果有记录通话号码,生成结束通知
|
||
if self.call_number:
|
||
duration = "Missed" # 默认为未接
|
||
|
||
# 根据通话是否曾经接通决定如何计算时长
|
||
if was_connected:
|
||
# 计算通话时长(秒)
|
||
if hasattr(self, 'call_connect_time'):
|
||
call_duration = round(time.time() - self.call_connect_time)
|
||
duration = str(call_duration)
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 通话结束,持续时间: {call_duration}秒")
|
||
|
||
# 发出通话结束信号
|
||
self.call_ended.emit(duration)
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 通话结束,号码: {self.call_number},持续时间: {duration}")
|
||
|
||
# 清除通话号码记录
|
||
self.call_number = ""
|
||
|
||
# 清除连接时间记录
|
||
if hasattr(self, 'call_connect_time'):
|
||
del self.call_connect_time
|
||
|
||
return calls
|
||
except Exception as e:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 获取通话状态出错: {str(e)}")
|
||
# 出错时返回空列表,并缓存空列表
|
||
self.cached_call_status = []
|
||
return []
|
||
|
||
def get_call_state_text(self):
|
||
"""
|
||
获取当前通话状态的文本描述
|
||
"""
|
||
calls = self.get_call_status()
|
||
if not calls:
|
||
return "无通话"
|
||
|
||
# 获取第一个通话的状态描述
|
||
call = calls[0]
|
||
stat = call.get('stat', -1)
|
||
state_text = self.call_states.get(stat, "未知状态")
|
||
|
||
# 添加方向信息
|
||
direction = "呼出" if call.get('dir', 0) == 0 else "呼入"
|
||
|
||
# 添加号码信息
|
||
number = call.get('number', '')
|
||
number_text = f", 号码: {number}" if number else ""
|
||
|
||
return f"{direction}通话, {state_text}{number_text}"
|
||
|
||
def is_call_connected(self):
|
||
"""检查通话是否已接通(不仅仅是振铃状态)"""
|
||
# 获取最新通话状态
|
||
calls = self.get_call_status()
|
||
|
||
# 如果没有通话,则未接通
|
||
if not calls:
|
||
return False
|
||
|
||
# 检查第一个通话是否处于活动状态(stat=0)
|
||
call = calls[0]
|
||
return call.get('stat', -1) == 0
|
||
|
||
def _ensure_pcm_audio_registered(self):
|
||
"""确保PCM音频已注册,用于通话音频处理"""
|
||
try:
|
||
# 检查当前PCM音频注册状态
|
||
pcm_status = self.send_at_command("AT+CPCMREG?")
|
||
time.sleep(0.03) # 30毫秒延迟
|
||
|
||
# 如果PCM没有注册,则进行注册
|
||
if not pcm_status or "+CPCMREG: 1" not in pcm_status:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 注册PCM音频")
|
||
reg_response = self.send_at_command("AT+CPCMREG=1")
|
||
|
||
if "OK" in reg_response:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注册成功")
|
||
self.pcm_audio_status.emit(True)
|
||
|
||
# 设置PCM音频格式
|
||
time.sleep(0.03) # 30毫秒延迟
|
||
frm_response = self.send_at_command("AT+CPCMFRM=1")
|
||
if "OK" in frm_response:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频格式设置成功")
|
||
else:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频格式设置失败")
|
||
else:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注册失败")
|
||
self.pcm_audio_status.emit(False)
|
||
else:
|
||
# 已经注册,发出信号
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频已注册")
|
||
self.pcm_audio_status.emit(True)
|
||
|
||
return True
|
||
except Exception as e:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 确保PCM音频注册出错: {str(e)}")
|
||
self.pcm_audio_status.emit(False)
|
||
return False
|
||
|
||
def _stop_pcm_audio(self):
|
||
"""停止PCM音频注册"""
|
||
try:
|
||
# 检查当前PCM音频注册状态
|
||
pcm_status = self.send_at_command("AT+CPCMREG?")
|
||
|
||
# 如果已注册,则注销
|
||
if pcm_status and "+CPCMREG: 1" in pcm_status:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 发送PCM音频注销命令")
|
||
response = self.send_at_command("AT+CPCMREG=0")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注销命令已发送")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注销响应: {response}")
|
||
|
||
if "OK" in response:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注销成功")
|
||
else:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频注销状态未知")
|
||
else:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频未注册或读取状态失败")
|
||
|
||
# 无论如何,都发送PCM音频已停止信号
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 发送PCM音频停止信号")
|
||
self.pcm_audio_status.emit(False)
|
||
|
||
return True
|
||
except Exception as e:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 停止PCM音频注册出错: {str(e)}")
|
||
return False
|
||
|
||
def _stop_all_ringtones(self):
|
||
"""停止所有铃声和声音"""
|
||
try:
|
||
# 这里不应该发送AT+CLIP=0,那会禁用来电显示
|
||
# 而是通知上层应用停止铃声播放
|
||
self.status_changed.emit("STOP_RINGTONES")
|
||
|
||
# 清理任何可能的铃声缓存
|
||
if hasattr(self, 'ringtone_thread') and self.ringtone_thread:
|
||
try:
|
||
self.ringtone_thread.stop()
|
||
self.ringtone_thread = None
|
||
except:
|
||
pass
|
||
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 停止铃声信号已发送")
|
||
return True
|
||
except Exception as e:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 停止铃声出错: {str(e)}")
|
||
return False
|
||
|
||
def _auto_detect_port(self):
|
||
"""尝试自动检测LTE模块连接的串口"""
|
||
try:
|
||
# 获取所有可用串口
|
||
available_ports = [port.device for port in serial.tools.list_ports.comports()]
|
||
print(f"检测到可用串口: {available_ports}")
|
||
|
||
if not available_ports:
|
||
self.status_changed.emit("未检测到任何串口设备")
|
||
print("未检测到任何串口设备")
|
||
return None
|
||
|
||
# 如果只有一个串口,直接返回
|
||
if len(available_ports) == 1:
|
||
print(f"只有一个串口可用,直接使用: {available_ports[0]}")
|
||
return available_ports[0]
|
||
|
||
# 如果有多个串口,尝试连接每个串口并发送AT命令
|
||
print("检测到多个串口,尝试查找LTE模块...")
|
||
for port in available_ports:
|
||
try:
|
||
print(f"尝试在串口 {port} 上查找LTE模块...")
|
||
# 尝试打开串口
|
||
test_serial = serial.Serial(
|
||
port=port,
|
||
baudrate=115200,
|
||
bytesize=serial.EIGHTBITS,
|
||
parity=serial.PARITY_NONE,
|
||
stopbits=serial.STOPBITS_ONE,
|
||
timeout=1,
|
||
write_timeout=1
|
||
)
|
||
|
||
# 清空缓冲区
|
||
test_serial.reset_input_buffer()
|
||
test_serial.reset_output_buffer()
|
||
|
||
# 发送AT命令并等待响应
|
||
print(f"向 {port} 发送AT命令")
|
||
test_serial.write(b'AT\r\n')
|
||
time.sleep(0.5)
|
||
|
||
# 读取响应
|
||
response = test_serial.read(test_serial.in_waiting or 100).decode('utf-8', errors='replace')
|
||
print(f"从 {port} 收到响应: {response}")
|
||
|
||
# 关闭测试连接
|
||
test_serial.close()
|
||
|
||
# 检查响应是否包含OK
|
||
if 'OK' in response:
|
||
self.status_changed.emit(f"自动检测到LTE模块连接在 {port}")
|
||
print(f"在 {port} 上找到LTE模块")
|
||
return port
|
||
except Exception as e:
|
||
print(f"测试 {port} 时出错: {str(e)}")
|
||
try:
|
||
# 确保串口已关闭
|
||
if 'test_serial' in locals() and test_serial.is_open:
|
||
test_serial.close()
|
||
except:
|
||
pass
|
||
continue
|
||
|
||
# 如果没有找到匹配的串口,返回COM6作为默认(如果存在)
|
||
if 'COM6' in available_ports:
|
||
self.status_changed.emit(f"未能确定LTE模块连接的串口,尝试使用COM6")
|
||
print(f"未能确定LTE模块连接的串口,尝试使用COM6")
|
||
return 'COM6'
|
||
|
||
# 否则返回第一个可用串口
|
||
self.status_changed.emit(f"未能确定LTE模块连接的串口,使用第一个可用串口 {available_ports[0]}")
|
||
print(f"未能确定LTE模块连接的串口,使用第一个可用串口 {available_ports[0]}")
|
||
return available_ports[0]
|
||
except Exception as e:
|
||
self.status_changed.emit(f"串口自动检测出错: {str(e)}")
|
||
return None
|
||
|
||
def _configure_module(self):
|
||
"""配置LTE模块的初始设置"""
|
||
try:
|
||
# 日志记录配置开始
|
||
self.status_changed.emit("初始化LTE模块")
|
||
|
||
# 禁用回显 - 可选,取决于模块和应用需求
|
||
# self.send_at_command("ATE0")
|
||
|
||
# 设置SMS文本模式
|
||
self.send_at_command("AT+CMGF=1")
|
||
|
||
# 设置SMS字符集
|
||
self.send_at_command('AT+CSCS="UCS2"')
|
||
|
||
# 设置来电显示
|
||
self.send_at_command("AT+CLIP=1")
|
||
|
||
# 设置新消息指示
|
||
self.send_at_command('AT+CNMI=2,2,0,0,0')
|
||
|
||
# 查询是否有PCM音频注册
|
||
pcm_status = self.send_at_command("AT+ECPCMREG?")
|
||
if "+ECPCMREG: 1" in pcm_status:
|
||
# PCM已注册,先取消注册
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频已注册,取消注册")
|
||
self._unregister_pcm_audio()
|
||
else:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - PCM音频未注册或读取状态失败")
|
||
# 确保PCM音频处于未注册状态
|
||
self.pcm_audio_status.emit(False)
|
||
|
||
# 获取基本信息
|
||
self._update_device_info()
|
||
|
||
self.status_changed.emit("LTE模块初始化完成")
|
||
return True
|
||
except Exception as e:
|
||
self.status_changed.emit(f"LTE模块配置失败: {str(e)}")
|
||
return False
|
||
|
||
def _update_device_info(self):
|
||
"""获取设备基本信息"""
|
||
try:
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 获取设备基本信息")
|
||
|
||
# 获取IMEI
|
||
response = self.send_at_command("AT+GSN")
|
||
if response and "ERROR" not in response:
|
||
self.imei = response.strip().split("\n")[0]
|
||
|
||
# 获取制造商
|
||
response = self.send_at_command("AT+GMI")
|
||
if response and "ERROR" not in response:
|
||
self.manufacturer = response.strip().split("\n")[0]
|
||
|
||
# 获取型号
|
||
response = self.send_at_command("AT+GMM")
|
||
if response and "ERROR" not in response:
|
||
self.model = response.strip().split("\n")[0]
|
||
|
||
# 获取固件版本
|
||
response = self.send_at_command("AT+GMR")
|
||
if response and "ERROR" not in response:
|
||
self.firmware = response.strip().split("\n")[0]
|
||
|
||
# 获取电话号码
|
||
response = self.send_at_command("AT+CNUM")
|
||
if response and "+CNUM:" in response:
|
||
match = re.search(r'\+CNUM: "([^"]*)",("?[^"]*"?),(\d+)', response)
|
||
if match:
|
||
self.phone_number = match.group(2).strip('"')
|
||
print(f"电话号码: {self.phone_number}")
|
||
|
||
# 获取运营商信息
|
||
response = self.send_at_command("AT+COPS?")
|
||
if response and "+COPS:" in response:
|
||
match = re.search(r'\+COPS: (\d+),(\d+),"([^"]*)"', response)
|
||
if match:
|
||
self.carrier = match.group(3)
|
||
print(f"运营商: {self.carrier}")
|
||
|
||
# 检查网络类型值,可能是第4个项目
|
||
if len(match.groups()) >= 4:
|
||
# 值映射: 0=GSM, 2=UTRAN, 7=LTE, 13=NR
|
||
net_type_map = {
|
||
'0': '2G (GSM)',
|
||
'2': '3G (UMTS)',
|
||
'7': '4G (LTE)',
|
||
'13': '5G (NR)'
|
||
}
|
||
net_type = match.group(4) if len(match.groups()) >= 4 else '7' # 默认LTE
|
||
self.network_type = net_type_map.get(net_type, f'Unknown ({net_type})')
|
||
else:
|
||
# 从response中提取网络类型
|
||
self._update_network_type()
|
||
else:
|
||
# 如果COPS查询失败,尝试单独更新网络类型
|
||
self._update_network_type()
|
||
|
||
# 获取信号强度
|
||
response = self.send_at_command("AT+CSQ")
|
||
if response and "+CSQ:" in response:
|
||
match = re.search(r'\+CSQ: (\d+),(\d+)', response)
|
||
if match:
|
||
rssi = int(match.group(1))
|
||
# 转换RSSI为信号格数和dBm值
|
||
if rssi == 99:
|
||
self.signal_strength = "无信号"
|
||
else:
|
||
# RSSI值0-31,对应-113dBm到-51dBm
|
||
dbm = -113 + (2 * rssi)
|
||
# 信号格数(0-4格)
|
||
if rssi >= 16: # >= -81dBm
|
||
bars = 4
|
||
elif rssi >= 12: # >= -89dBm
|
||
bars = 3
|
||
elif rssi >= 8: # >= -97dBm
|
||
bars = 2
|
||
elif rssi >= 4: # >= -105dBm
|
||
bars = 1
|
||
else:
|
||
bars = 0
|
||
|
||
self.signal_strength = f"{bars}格 ({dbm}dBm)"
|
||
print(f"信号强度: {self.signal_strength}")
|
||
|
||
# 发送初始化完成的信号
|
||
self.status_changed.emit(f"设备信息已更新: {self.model} {self.carrier}")
|
||
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 设备基本信息获取完成")
|
||
return True
|
||
except Exception as e:
|
||
self.status_changed.emit(f"获取设备信息失败: {str(e)}")
|
||
return False
|
||
|
||
def _update_network_type(self):
|
||
"""单独更新网络类型信息"""
|
||
try:
|
||
# 尝试使用AT+CEREG?命令获取LTE网络注册状态
|
||
cereg_response = self.send_at_command("AT+CEREG?")
|
||
if "CEREG: " in cereg_response:
|
||
# 检查是否有网络注册
|
||
match = re.search(r'\+CEREG: \d+,(\d+)', cereg_response)
|
||
if match and match.group(1) in ['1', '5']: # 1=已注册,本地网络; 5=已注册,漫游
|
||
self.network_type = "4G (LTE)"
|
||
return
|
||
|
||
# 尝试使用AT+CREG?命令获取GSM/UMTS网络注册状态
|
||
creg_response = self.send_at_command("AT+CREG?")
|
||
if "CREG: " in creg_response:
|
||
match = re.search(r'\+CREG: \d+,(\d+)', creg_response)
|
||
if match and match.group(1) in ['1', '5']:
|
||
# 进一步检查是2G还是3G
|
||
cgreg_response = self.send_at_command("AT+CGREG?")
|
||
if "CGREG: " in cgreg_response and re.search(r'\+CGREG: \d+,[15]', cgreg_response):
|
||
self.network_type = "3G (UMTS)"
|
||
else:
|
||
self.network_type = "2G (GSM)"
|
||
return
|
||
|
||
# 如果以上都失败,使用默认值
|
||
self.network_type = "未知"
|
||
except Exception as e:
|
||
print(f"更新网络类型失败: {str(e)}")
|
||
self.network_type = "更新失败" |