用Python脚本自动化测试EC20模块的AT指令实战指南

每次调试EC20模块时,在串口工具里反复输入AT指令的场景是否让你感到疲惫?作为一名长期与物联网模组打交道的开发者,我深刻理解手动测试的低效与痛苦——敲错一个字符就得重来,测试不同网络状态需要反复插拔SIM卡,批量验证功能时更是耗时耗力。本文将分享如何用Python的serial库构建自动化测试方案,让你从此告别重复劳动。

1. 环境搭建与基础连接

在开始编写自动化脚本前,需要准备以下硬件和软件环境:

  • 硬件准备

    • EC20模块开发板(带USB接口)
    • 支持4G网络的SIM卡
    • USB转串口线(如CP2102)
    • 可选:外接天线(增强信号稳定性)
  • Python库安装

pip install pyserial
pip install pytest  # 用于后续自动化测试框架

连接EC20模块后,首先需要确认设备节点。在Linux系统下,通常为 /dev/ttyUSB0 /dev/ttyACM0 ;Windows下则为 COM3 等端口。可以通过以下代码快速检测可用串口:

import serial.tools.list_ports

ports = serial.tools.list_ports.comports()
for port in ports:
    print(port.device)

建立基础连接时,需要注意三个关键参数:波特率(默认115200)、超时时间和流控设置。以下是推荐的基础连接配置:

import serial

ser = serial.Serial(
    port='/dev/ttyUSB0',
    baudrate=115200,
    timeout=1,  # 秒
    rtscts=False,  # 禁用硬件流控
    dsrdtr=False   # 禁用DTR流控
)

提示:如果遇到连接问题,尝试在EC20模块上执行 AT+IPR? 确认当前波特率,或使用 AT+IFC=0,0 关闭流控。

2. AT指令交互封装实战

直接使用串口发送原始指令不仅容易出错,也难以维护。我们需要构建一个健壮的指令交互层,包含以下核心功能:

  • 自动添加 \r\n 结束符
  • 统一处理模块响应
  • 支持超时重试机制
  • 错误代码解析

2.1 基础指令发送函数

def send_at_command(ser, command, expected_response="OK", timeout=3, retries=3):
    original_timeout = ser.timeout
    ser.timeout = timeout
    
    for attempt in range(retries):
        ser.write(f"{command}\r\n".encode())
        response = b''
        
        while True:
            line = ser.readline()
            if not line:
                break
            response += line
            
        decoded = response.decode().strip()
        if expected_response in decoded:
            return decoded
            
    raise Exception(f"Command {command} failed after {retries} retries")

2.2 常用指令封装示例

将高频使用的AT指令封装成语义化的函数,大幅提升代码可读性:

def get_signal_quality(ser):
    response = send_at_command(ser, "AT+CSQ")
    # 典型响应:+CSQ: 24,99
    parts = response.split(":")[1].strip().split(",")
    return {"rssi": int(parts[0]), "ber": int(parts[1])}

def check_network_registration(ser):
    response = send_at_command(ser, "AT+CREG?")
    # 响应示例:+CREG: 2,1,"3747","A23C2",100
    status = int(response.split(",")[1])
    return {
        0: "未注册",
        1: "已注册",
        2: "正在注册",
        3: "注册被拒绝"
    }.get(status, "未知状态")

2.3 错误处理增强

EC20模块支持通过 AT+CMEE=2 开启详细错误模式,这对调试至关重要:

def enable_verbose_errors(ser):
    send_at_command(ser, "AT+CMEE=2")

def get_sim_info(ser):
    try:
        iccid = send_at_command(ser, "AT+QCCID")
        imei = send_at_command(ser, "AT+GSN")
        return {"iccid": iccid.split(":")[1].strip(), 
                "imei": imei.strip()}
    except Exception as e:
        print(f"获取SIM卡信息失败: {str(e)}")
        return None

3. 构建自动化测试框架

当需要批量验证模块功能时,一个结构化的测试框架能显著提升效率。我们基于pytest构建可扩展的测试套件。

3.1 测试用例设计

创建 test_ec20.py 文件,包含核心功能测试:

import pytest

@pytest.fixture(scope="module")
def ec20_connection():
    ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=1)
    yield ser
    ser.close()

def test_signal_quality(ec20_connection):
    quality = get_signal_quality(ec20_connection)
    assert 0 <= quality['rssi'] <= 31, "信号强度值异常"

def test_network_registration(ec20_connection):
    status = check_network_registration(ec20_connection)
    assert status == "已注册", f"网络注册失败: {status}"

def test_sim_card(ec20_connection):
    info = get_sim_info(ec20_connection)
    assert info['iccid'] and len(info['iccid']) == 20, "ICCID无效"
    assert info['imei'] and len(info['imei']) == 15, "IMEI无效"

3.2 参数化测试

使用pytest的参数化功能批量测试不同配置:

@pytest.mark.parametrize("baudrate", [9600, 19200, 38400, 57600, 115200])
def test_baudrate_change(ec20_connection, baudrate):
    send_at_command(ec20_connection, f"AT+IPR={baudrate}")
    send_at_command(ec20_connection, "AT&W")  # 保存设置
    # 需要重新初始化连接
    new_ser = serial.Serial('/dev/ttyUSB0', baudrate, timeout=1)
    assert new_ser.baudrate == baudrate
    new_ser.close()

3.3 测试报告生成

添加以下命令生成美观的HTML报告:

pytest --html=report.html --self-contained-html

4. 高级应用与性能优化

当基础功能稳定后,可以进一步优化测试效率和扩展性。

4.1 并发测试策略

使用多线程同时测试多个功能模块:

from threading import Thread

def concurrent_test():
    results = {}
    
    def test_func(target, name):
        results[name] = target()
    
    threads = [
        Thread(target=test_func, args=(lambda: get_signal_quality(ser), "signal")),
        Thread(target=test_func, args=(lambda: check_network_registration(ser), "network")),
        Thread(target=test_func, args=(lambda: get_sim_info(ser), "sim"))
    ]
    
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    
    return results

4.2 响应时间分析

通过装饰器记录关键操作的执行时间:

import time
from functools import wraps

def timing(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} executed in {end-start:.3f}s")
        return result
    return wrapper

@timing
def extended_test_sequence(ser):
    send_at_command(ser, "AT+CSQ")
    send_at_command(ser, "AT+CREG?")
    send_at_command(ser, "AT+GSN")

4.3 自动化测试CI集成

将测试脚本集成到Jenkins或GitHub Actions中,实现持续验证:

# .github/workflows/test.yml 示例
name: EC20 Module Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.9'
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install pyserial pytest pytest-html
    - name: Run tests
      run: |
        python -m pytest test_ec20.py --html=report.html --self-contained-html
    - name: Upload report
      uses: actions/upload-artifact@v2
      with:
        name: test-report
        path: report.html

在实际项目中,这套自动化测试方案将测试时间从原来手动操作的30分钟缩短到2分钟以内,且避免了人为错误。一个特别有用的技巧是在测试前自动执行 AT+CMEE=2 开启详细错误模式,这让我们快速定位到一处波特率配置不匹配的问题,节省了大量调试时间。

更多推荐