用Python调试拨号网络

30 阅读7分钟

我来提供几个可直接运行的真实代码片段,用于调试拨号网络:

1. 基础AT命令交互片段

# dial_basic.py - 基础拨号调试

import serial
import time
import sys

def test_basic_at_commands(port='COM3', baudrate=115200):
    """基本AT命令测试 - 立即运行"""
    try:
        # 1. 打开串口
        ser = serial.Serial(
            port=port,
            baudrate=baudrate,
            bytesize=8,
            parity='N',
            stopbits=1,
            timeout=2,
            rtscts=True  # 硬件流控
        )
        print(f"✓ 已连接 {port} @ {baudrate}bps")
        
        # 2. 清空缓冲区
        ser.reset_input_buffer()
        ser.reset_output_buffer()
        time.sleep(0.5)
        
        # 3. 测试基本命令
        commands = [
            'AT',            # 测试连接
            'ATE1',          # 打开回显
            'ATV1',          # 详细响应模式
            'AT+CGMI',       # 制造商
            'AT+CGMM',       # 型号
            'AT+CGMR',       # 版本
            'AT+CGSN',       # IMEI
            'AT+CSQ',        # 信号强度
            'AT+COPS?',      # 运营商
            'AT+CREG?',      # 网络注册
        ]
        
        for cmd in commands:
            print(f"\n[发送] {cmd}")
            ser.write((cmd + '\r\n').encode())
            
            # 读取响应
            time.sleep(1)
            if ser.in_waiting:
                response = ser.read(ser.in_waiting).decode('ascii', errors='ignore')
                print(f"[响应] {response.strip()}")
            
            time.sleep(0.5)
        
        ser.close()
        
    except Exception as e:
        print(f"✗ 错误: {e}")
        sys.exit(1)

# 立即运行
if __name__ == "__main__":
    # Windows: COM1-COM10, Linux: /dev/ttyUSB0, /dev/ttyACM0
    test_basic_at_commands('COM3', 115200)
    

2. 真实拨号连接片段

# dial_connection.py - 真实拨号连接

import serial
import time
import re

def dial_and_connect(port='COM3', phone_number='*99#'):
    """执行实际拨号连接"""
    
    def send_and_wait(ser, command, wait_for=None, timeout=5):
        """发送命令并等待特定响应"""
        print(f">>> {command}")
        ser.write((command + '\r\n').encode())
        
        start_time = time.time()
        buffer = ""
        
        while time.time() - start_time < timeout:
            if ser.in_waiting:
                chunk = ser.read(ser.in_waiting).decode('ascii', errors='ignore')
                buffer += chunk
                print(f"<<< {chunk}", end='')
                
                if wait_for and wait_for in buffer:
                    return buffer
            
            time.sleep(0.1)
        
        return buffer
    
    try:
        # 初始化串口
        ser = serial.Serial(port, 115200, timeout=2, rtscts=True)
        time.sleep(2)
        
        print("="*50)
        print("开始拨号调试流程...")
        print("="*50)
        
        # 阶段1: 初始化Modem
        print("\n[阶段1] Modem初始化")
        send_and_wait(ser, 'ATZ')        # 重置
        send_and_wait(ser, 'ATE0')       # 关闭回显
        send_and_wait(ser, 'AT+CFUN=1')  # 全功能模式
        
        # 阶段2: 检查网络状态
        print("\n[阶段2] 网络状态检查")
        send_and_wait(ser, 'AT+CPIN?')   # SIM卡状态
        send_and_wait(ser, 'AT+CSQ')     # 信号强度
        send_and_wait(ser, 'AT+CREG?')   # 网络注册
        send_and_wait(ser, 'AT+CGREG?')  # GPRS注册
        
        # 阶段3: 配置PDP上下文 (GPRS/3G/4G)
        print("\n[阶段3] 配置数据连接")
        send_and_wait(ser, 'AT+CGDCONT=1,"IP","cmnet"')  # 设置APN
        send_and_wait(ser, 'AT+CGACT=1,1')               # 激活PDP
        
        # 阶段4: 实际拨号
        print(f"\n[阶段4] 拨号连接: {phone_number}")
        response = send_and_wait(ser, f'ATD{phone_number}', 'CONNECT', 30)
        
        # 阶段5: 分析连接结果
        if 'CONNECT' in response:
            print("\n✅ 拨号连接成功!")
            print(f"连接速率: {re.findall(r'CONNECT (\d+)', response)} bps")
            
            # 获取IP地址
            send_and_wait(ser, 'AT+CGPADDR=1')
            
            # 进入数据模式(这里可以开始发送PPP帧或TCP数据)
            print("\n进入数据模式...")
            print("按 Ctrl+C 中断连接")
            
            try:
                # 保持连接,监听数据
                while True:
                    if ser.in_waiting:
                        data = ser.read(ser.in_waiting)
                        try:
                            print(data.decode('ascii', errors='ignore'), end='')
                        except:
                            print(data.hex(), end=' ')
                    time.sleep(0.1)
            except KeyboardInterrupt:
                print("\n用户中断连接")
                
        elif 'NO CARRIER' in response:
            print("\n❌ 无载波信号")
        elif 'BUSY' in response:
            print("\n❌ 线路忙")
        elif 'ERROR' in response:
            print("\n❌ AT命令错误")
        else:
            print("\n⚠️  未知响应")
            print(f"完整响应:\n{response}")
        
        # 挂断
        print("\n[清理] 挂断连接")
        send_and_wait(ser, 'ATH')
        ser.close()
        
    except KeyboardInterrupt:
        print("\n用户终止程序")
    except Exception as e:
        print(f"错误: {e}")
        sys.exit(1)

if __name__ == "__main__":
    # 根据不同运营商修改号码和APN
    # 中国移动: *99***1# 或 *99#, APN: cmnet
    # 中国联通: *99#, APN: 3gnet
    # 中国电信: #777, APN: ctnet
    
    dial_and_connect('COM3', '*99#')

3. PPP拨号调试片段

# ppp_dialer.py - PPP协议拨号调试

import serial
import time
import struct
import binascii

class PPPDebugger:
    """PPP协议调试器"""
    
    def __init__(self, port='COM3'):
        self.ser = serial.Serial(port, 115200, timeout=0.1)
        self.ppp_state = 'INIT'
        
    def send_ppp_frame(self, protocol, data):
        """发送PPP帧"""
        # PPP帧格式: Flag(0x7E) + Address(0xFF) + Control(0x03) + Protocol + Info + FCS + Flag(0x7E)
        frame = struct.pack('!BBH', 0xFF, 0x03, protocol) + data
        
        # 计算FCS(简单版本)
        fcs = self.calc_fcs(frame)
        frame += struct.pack('!H', fcs)
        
        # 字符填充和添加标志位
        frame = self.escape_frame(frame)
        frame = b'\x7E' + frame + b'\x7E'
        
        self.ser.write(frame)
        print(f"发送PPP帧: 协议=0x{protocol:04X}, 长度={len(data)}")
        
    def escape_frame(self, data):
        """PPP字符填充"""
        escaped = b''
        for byte in data:
            if byte == 0x7E:
                escaped += b'\x7D\x5E'
            elif byte == 0x7D:
                escaped += b'\x7D\x5D'
            else:
                escaped += bytes([byte])
        return escaped
    
    def calc_fcs(self, data):
        """计算PPP帧校验序列(简化版)"""
        fcs = 0xFFFF
        for byte in data:
            fcs ^= byte
            for _ in range(8):
                if fcs & 0x0001:
                    fcs = (fcs >> 1) ^ 0x8408
                else:
                    fcs >>= 1
        return fcs ^ 0xFFFF
    
    def lcp_negotiation(self):
        """LCP协商过程"""
        print("开始LCP协商...")
        
        # 发送LCP配置请求
        lcp_config = struct.pack('!BBHI', 0x01, 0x01, 0x00, 0x01)  # 类型,ID,长度,魔术数
        self.send_ppp_frame(0xC021, lcp_config)
        
        # 监听响应
        start_time = time.time()
        while time.time() - start_time < 10:
            if self.ser.in_waiting:
                frame = self.ser.read(self.ser.in_waiting)
                if b'\x7E' in frame:
                    print(f"收到PPP帧: {binascii.hexlify(frame).decode()}")
                    # 解析PPP帧
                    if frame[0] == 0x7E and frame[-1] == 0x7E:
                        # 简单的帧解析
                        print("有效的PPP帧")
                        break
            time.sleep(0.1)
    
    def debug_ppp_session(self):
        """调试PPP会话"""
        print("PPP调试模式启动...")
        
        # 1. 发送AT命令建立连接
        self.ser.write(b'ATDT*99#\r\n')
        time.sleep(3)
        
        # 2. 进入PPP协商
        self.lcp_negotiation()
        
        # 3. 监听PPP数据
        print("\n监听PPP数据流...")
        try:
            while True:
                if self.ser.in_waiting:
                    data = self.ser.read(self.ser.in_waiting)
                    if data:
                        # 显示为hex和ascii
                        hex_str = binascii.hexlify(data).decode()
                        ascii_str = ''.join([chr(c) if 32 <= c < 127 else '.' for c in data])
                        
                        print(f"HEX: {hex_str[:64]}...")
                        print(f"ASCII: {ascii_str[:32]}...")
                        print("-" * 50)
                time.sleep(0.5)
        except KeyboardInterrupt:
            print("结束PPP调试")

if __name__ == "__main__":
    debugger = PPPDebugger('COM3')
    debugger.debug_ppp_session()

4. 信号质量监控片段

# signal_monitor.py - 实时信号监控

import serial
import time
import re
from collections import deque
import matplotlib.pyplot as plt
import numpy as np

def monitor_signal_quality(port='COM3', duration=300):
    """实时监控信号质量"""
    
    ser = serial.Serial(port, 115200, timeout=1)
    signal_history = deque(maxlen=100)
    time_history = deque(maxlen=100)
    
    print("开始信号质量监控...")
    print("按 Ctrl+C 停止\n")
    
    start_time = time.time()
    update_count = 0
    
    try:
        # 初始化Modem
        ser.write(b'ATE0\r\n')
        time.sleep(1)
        ser.reset_input_buffer()
        
        while time.time() - start_time < duration:
            # 发送CSQ命令
            ser.write(b'AT+CSQ\r\n')
            time.sleep(1)
            
            # 读取响应
            if ser.in_waiting:
                response = ser.read(ser.in_waiting).decode('ascii', errors='ignore')
                
                # 解析信号强度
                match = re.search(r'CSQ:\s*(\d+),', response)
                if match:
                    rssi = int(match.group(1))
                    
                    if rssi != 99:  # 99表示无效
                        # 转换为dBm (近似)
                        if rssi == 0:
                            dbm = -113
                        elif 1 <= rssi <= 31:
                            dbm = -113 + (rssi * 2)
                        else:
                            dbm = -51
                        
                        current_time = time.time() - start_time
                        signal_history.append(dbm)
                        time_history.append(current_time)
                        update_count += 1
                        
                        # 显示当前信号
                        bars = '█' * min(rssi, 20)
                        print(f"[{current_time:6.1f}s] RSSI: {rssi:2d} | {bars:20s} | {dbm:4d} dBm")
                        
                        # 每10次更新显示统计
                        if update_count % 10 == 0:
                            if signal_history:
                                avg = np.mean(list(signal_history))
                                std = np.std(list(signal_history))
                                print(f"统计: 平均={avg:.1f}dBm, 波动={std:.1f}dBm")
                                print("-" * 50)
            
            time.sleep(2)  # 每2秒采样一次
            
    except KeyboardInterrupt:
        print("\n监控停止")
    finally:
        ser.close()
        
        # 绘制信号图
        if signal_history:
            plt.figure(figsize=(10, 6))
            plt.plot(time_history, signal_history, 'b-', linewidth=2)
            plt.xlabel('时间 (秒)')
            plt.ylabel('信号强度 (dBm)')
            plt.title('信号强度历史')
            plt.grid(True, alpha=0.3)
            plt.savefig('signal_quality.png')
            plt.show()

if __name__ == "__main__":
    monitor_signal_quality('COM3', duration=60)  # 监控60秒

5. 快速测试脚本

# quick_dial_test.py - 一键拨号测试

import serial
import sys

def quick_test():
    """快速测试拨号连接"""
    port = input("请输入串口 (默认 COM3): ") or "COM3"
    number = input("请输入拨号号码 (默认 *99#): ") or "*99#"
    
    print(f"\n测试配置:")
    print(f"  串口: {port}")
    print(f"  号码: {number}")
    print(f"  波特率: 115200")
    print("-" * 40)
    
    try:
        ser = serial.Serial(port, 115200, timeout=2)
        print("✓ 串口打开成功")
        
        # 发送测试命令
        test_cmds = [
            'AT',
            'AT+CSQ',
            'AT+COPS?',
            f'ATD{number}',
        ]
        
        for cmd in test_cmds:
            print(f"\n>>> {cmd}")
            ser.write((cmd + '\r\n').encode())
            
            # 等待并显示响应
            import time
            time.sleep(2)
            
            if ser.in_waiting:
                response = ser.read(ser.in_waiting).decode('ascii', errors='ignore')
                print(f"<<< {response.strip()}")
                
                # 检查是否连接成功
                if 'CONNECT' in response:
                    print("\n✅ 连接成功!")
                    print("可以开始PPP协商或数据传输")
                    
                    # 保持连接5秒
                    time.sleep(5)
                    
                    # 挂断
                    ser.write(b'ATH\r\n')
                    print("连接已挂断")
                    break
        
        ser.close()
        
    except Exception as e:
        print(f"✗ 错误: {e}")
        return 1
    
    return 0

if __name__ == "__main__":
    sys.exit(quick_test())

使用说明 安装依赖: bash 复制 下载 pip install pyserial matplotlib numpy 确定串口号: Windows:设备管理器查看端口 Linux:ls /dev/tty* 查看设备 运行测试: bash 复制 下载 # 基础AT命令测试 python dial_basic.py # 拨号连接测试 python dial_connection.py # 信号监控 python signal_monitor.py 常见问题解决: 如果提示"Access Denied",可能是串口被其他程序占用 如果无响应,尝试不同的波特率(9600, 19200, 38400, 57600, 115200) GSM/3G/4G Modem需要SIM卡且已开通数据业务 这些代码片段可以直接运行,帮助你真实地调试拨号网络连接!