1. SECS-II 消息结构解析与手工构造
任务目标
手工构造一条 SECS-II 消息(S1F1,W-Bit 置位),消息体包含 L、A、B、BOOLEAN、U1/U2/U4、I1/I2/I4、F4/F8 等数据项,并编写一个解析器把字节流还原成结构化数据,验证构造与解析一致。
SECS-II 编码规则(本案例用到的部分)
- 每个数据项 = 格式字节 + 长度字节(1~3 字节)+ 数据。
- 格式字节:高 6 位是格式码,低 2 位是长度字节数(0 表示 1 字节长度)。
- 格式码:L=0x00,B=0x20,BOOLEAN=0x24,A=0x40,I1=0x64,I2=0x68,I4=0x70,F4=0x90,F8=0x98,U1=0xA4,U2=0xA8,U4=0xB0。
- 长度字段表示“数据字节数”,不是元素个数;元素个数 = 数据字节数 / 单元素字节数。
完整实现(Python 3,无第三方依赖)
import struct
# 格式码 -> (名称, 单元素字节数, 是否列表)
FMT = {
0x00: ("L", 0, True),
0x20: ("B", 1, False),
0x24: ("BOOLEAN", 1, False),
0x40: ("A", 1, False),
0x64: ("I1", 1, False),
0x68: ("I2", 2, False),
0x70: ("I4", 4, False),
0x90: ("F4", 4, False),
0x98: ("F8", 8, False),
0xA4: ("U1", 1, False),
0xA8: ("U2", 2, False),
0xB0: ("U4", 4, False),
}
def _len_bytes(n):
if n < 0x100: return 1
if n < 0x10000: return 2
return 3
def encode_item(fmt_code, data):
name, esize, is_list = FMT[fmt_code]
if is_list:
body = b"".join(data) # data 是已编码子项列表
elif name == "A":
body = data.encode("ascii")
elif name == "BOOLEAN":
body = bytes(1 if x else 0 for x in data)
elif name == "B":
body = bytes(data)
else:
body = b"".join(struct.pack(">" + {1:"B",2:"H",4:"I",8:"Q"}[esize], v) for v in data)
n = len(body)
lb = _len_bytes(n)
fmt_byte = (fmt_code & 0xFC) | (lb - 1)
return bytes([fmt_byte]) + n.to_bytes(lb, "big") + body
def decode_item(buf, pos):
fmt_byte = buf[pos]; pos += 1
fmt_code = fmt_byte & 0xFC
lb = (fmt_byte & 0x03) + 1
length = int.from_bytes(buf[pos:pos+lb], "big"); pos += lb
name, esize, is_list = FMT[fmt_code]
if is_list:
items = []
end = pos + length
while pos < end:
item, pos = decode_item(buf, pos)
items.append(item)
return {"fmt": name, "value": items}, pos
raw = buf[pos:pos+length]; pos += length
if name == "A":
val = raw.decode("ascii")
elif name == "BOOLEAN":
val = [bool(b) for b in raw]
elif name == "B":
val = list(raw)
else:
cnt = length // esize
val = list(struct.unpack(">" + {1:"B",2:"H",4:"I",8:"Q"}[esize] * cnt, raw))
return {"fmt": name, "value": val}, pos
def build_message(stream, function, wbit, body):
header = struct.pack(">HBBBBI",
0x0001, # DeviceID
(0x80 if wbit else 0) | stream,
function, 0x00, 0x00, 0x00000001)
return header + body
def parse_message(buf):
devid, sf, func, ptype, stype, sysb = struct.unpack(">HBBBBI", buf[:10])
wbit = bool(sf & 0x80)
stream = sf & 0x7F
body, _ = decode_item(buf, 10)
return {"device": devid, "wbit": wbit, "stream": stream,
"function": func, "system_bytes": sysb, "body": body}
# ---- 手工构造消息体 ----
body = b"".join([
encode_item(0x40, "HELLO"), # A
encode_item(0x20, [0xDE, 0xAD]), # B
encode_item(0x24, [True, False, True]), # BOOLEAN
encode_item(0xA4, [1, 2, 255]), # U1
encode_item(0xA8, [1000, 2000]), # U2
encode_item(0xB0, [70000]), # U4
encode_item(0x64, [-1, 127]), # I1
encode_item(0x68, [-300, 300]), # I2
encode_item(0x70, [-100000]), # I4
encode_item(0x90, [1.5, -2.25]), # F4
encode_item(0x98, [3.141592653589793]), # F8
])
body = encode_item(0x00, [body]) # 外层 L
msg = build_message(1, 1, True, body)
print("HEX:", msg.hex(" ").upper())
parsed = parse_message(msg)
print("HEADER:", parsed["device"], parsed["wbit"], parsed["stream"],
parsed["function"], parsed["system_bytes"])
print("BODY:", parsed["body"])
运行与验证
- 保存为
secs2_demo.py,执行python3 secs2_demo.py。 - 观察输出:第一行是完整消息的十六进制串,第二行是解析出的头部字段,第三行是解析出的结构化消息体。
- 通过条件:解析结果中
wbit=True、stream=1、function=1;body为 L 列表,其value依次包含 A="HELLO"、B=[0xDE,0xAD]、BOOLEAN=[True,False,True]、U1=[1,2,255]、U2=[1000,2000]、U4=[70000]、I1=[-1,127]、I2=[-300,300]、I4=[-100000]、F4≈[1.5,-2.25]、F8≈[3.141592653589793],且与构造时输入完全一致。 - 若需进一步验证,可把
msg.hex()输出粘贴到十六进制编辑器(如 HxD、xxd)中,对照格式字节与长度字节逐项核对。
常见问题
- 长度字段是“字节数”而非“元素个数”,U2 有 2 个元素时长度应为 4。
- 格式字节低 2 位是长度字节数减 1,长度 ≥ 256 时需用 2 字节长度。
- BOOLEAN 每个元素占 1 字节,0 为 False,非 0 为 True。
- F4/F8 使用 IEEE 754 大端序,Python 的
struct默认网络字节序即为大端。