4. SECS-I 串口通信基础

任务说明

本案例用 Python 通过 RS-232 串口发送一条 SECS-I 协议的 S1F1(Are You There)消息,并解析设备返回的 S1F2 消息。SECS-I 帧结构为:长度头(10 字节 ASCII 十进制,不足补 0)+ 消息体。消息体由设备 ID、W-bit、Stream/Function、Block 号、系统字节、数据长度和 SECS-II 数据组成。

环境依赖

pip install pyserial

需要一根 RS-232 串口线(或 USB 转串口),设备端已配置好 SECS-I 参数(波特率、数据位、停止位、校验位)。本案例默认 9600-8-N-1。

SECS-I 帧结构

长度头 10字节ASCII 设备ID 2字节 W-bit 1字节 S/F 1字节 Block 1字节 系统字节 2字节 数据长度 2字节 SECS-II 数据 变长 S1F1 数据长度=0(无数据体) S1F2 数据长度=0(设备回复在线)

完整代码

import serial
import struct
import time

# ---------- SECS-I 帧封装 ----------
def build_secs1_frame(device_id, stream, function, wbit=0, system_bytes=0, data=b""):
    """构造 SECS-I 帧:10字节长度头 + 消息体"""
    # 消息体:设备ID(2) + W-bit(1) + S/F(1) + Block(1) + 系统字节(2) + 数据长度(2) + 数据
    sf = (stream & 0x7F) << 1 | (function & 0x7F)
    body = struct.pack(">H", device_id)          # 设备ID 2字节
    body += bytes([wbit & 0x80])                  # W-bit 1字节
    body += bytes([sf])                            # Stream/Function 1字节
    body += bytes([0])                             # Block 号 1字节
    body += struct.pack(">H", system_bytes)      # 系统字节 2字节
    body += struct.pack(">H", len(data))         # 数据长度 2字节
    body += data
    # 长度头:10字节 ASCII 十进制,不足补0
    header = f"{len(body):010d}".encode("ascii")
    return header + body

# ---------- SECS-I 帧解析 ----------
def parse_secs1_frame(raw):
    """解析 SECS-I 帧,返回字典"""
    if len(raw) < 10:
        raise ValueError("帧长度不足10字节")
    length = int(raw[:10].decode("ascii"))
    body = raw[10:10 + length]
    if len(body) < 10:
        raise ValueError("消息体不完整")
    device_id = struct.unpack(">H", body[0:2])[0]
    wbit = body[2]
    sf = body[3]
    stream = (sf >> 1) & 0x7F
    function = sf & 0x7F
    block = body[4]
    system_bytes = struct.unpack(">H", body[5:7])[0]
    data_len = struct.unpack(">H", body[7:9])[0]
    data = body[9:9 + data_len]
    return {
        "device_id": device_id, "wbit": wbit,
        "stream": stream, "function": function,
        "block": block, "system_bytes": system_bytes,
        "data_len": data_len, "data": data,
    }

# ---------- 主流程 ----------
def main():
    ser = serial.Serial(
        port="COM3",          # Linux 下改为 /dev/ttyUSB0
        baudrate=9600,
        bytesize=serial.EIGHTBITS,
        parity=serial.PARITY_NONE,
        stopbits=serial.STOPBITS_ONE,
        timeout=5,
    )
    try:
        # 发送 S1F1
        frame = build_secs1_frame(device_id=0, stream=1, function=1)
        ser.write(frame)
        print("已发送 S1F1,等待回复...")

        # 接收回复:先读10字节长度头
        header = ser.read(10)
        if len(header) < 10:
            print("超时:未收到长度头")
            return
        length = int(header.decode("ascii"))
        body = ser.read(length)
        reply = parse_secs1_frame(header + body)
        print(f"收到 S{reply['stream']}F{reply['function']} "
              f"设备ID={reply['device_id']} 数据长度={reply['data_len']}")
        if reply["stream"] == 1 and reply["function"] == 2:
            print("验证通过:设备回复 S1F2,通信正常")
        else:
            print("收到非预期消息")
    finally:
        ser.close()

if __name__ == "__main__":
    main()

运行方式

python secs1_s1f1.py

将 port 改为实际串口号(Windows 如 COM3,Linux 如 /dev/ttyUSB0)。

验证方法与通过条件

  • 运行后终端打印“已发送 S1F1,等待回复...”。
  • 若设备在线并支持 SECS-I,终端打印“收到 S1F2 设备ID=0 数据长度=0”及“验证通过”。
  • 若 5 秒内无回复,打印“超时:未收到长度头”,说明串口参数或设备连接需检查。
  • 通过条件:收到 Stream=1、Function=2 的回复帧,且长度头解析无异常。

常见问题

  • 串口号错误:serial.SerialException,确认设备管理器中端口号。
  • 波特率不匹配:收到乱码或长度头解析失败,确认设备端 SECS-I 参数。
  • 设备 ID 不匹配:部分设备要求非 0 的 device_id,按设备手册修改。