* 增加 J519 稠密发送采样校验与保姿回发逻辑 * 调整 saveTrajectory 导出与 sequence buffer 行为 * 补充 10010 解析脚本、ICSP 说明和回归测试
200 lines
7.1 KiB
Lua
200 lines
7.1 KiB
Lua
local plugin_info = {
|
||
version = "1.1.0",
|
||
author = "OpenAI Codex",
|
||
description = "FANUC TCP 10010 状态帧解析器",
|
||
}
|
||
|
||
set_plugin_info(plugin_info)
|
||
|
||
local fanuc10010_proto = Proto("fanuc10010_state_frame", "FANUC 10010 State Frame")
|
||
|
||
local FRAME_LENGTH = 90
|
||
local HEADER_LENGTH = 3
|
||
local TRAILER_LENGTH = 3
|
||
local LENGTH_OFFSET = 3
|
||
local MESSAGE_ID_OFFSET = 7
|
||
local POSE_OFFSET = 11
|
||
local POSE_COUNT = 6
|
||
local JOINT_OFFSET = 35
|
||
local JOINT_COUNT = 9
|
||
local TAIL_OFFSET = 71
|
||
local TAIL_COUNT = 4
|
||
|
||
local POSE_NAMES = { "X", "Y", "Z", "W", "P", "R" }
|
||
local JOINT_NAMES = { "J1", "J2", "J3", "J4", "J5", "J6", "Ext1", "Ext2", "Ext3" }
|
||
local HEADER_MAGIC = "doz"
|
||
local TRAILER_MAGIC = "zod"
|
||
|
||
local fields = {
|
||
frame = ProtoField.bytes("fanuc10010.frame", "原始状态帧"),
|
||
header = ProtoField.string("fanuc10010.header", "帧头 Magic"),
|
||
declared_length = ProtoField.uint32("fanuc10010.length", "声明长度", base.DEC),
|
||
message_id = ProtoField.uint32("fanuc10010.message_id", "消息号", base.DEC),
|
||
pose = ProtoField.none("fanuc10010.pose", "笛卡尔位姿"),
|
||
joints = ProtoField.none("fanuc10010.joints", "关节与扩展轴"),
|
||
joint_degrees = ProtoField.none("fanuc10010.joint_degrees", "Joint Degrees"),
|
||
tail = ProtoField.none("fanuc10010.tail", "尾部状态字"),
|
||
trailer = ProtoField.string("fanuc10010.trailer", "帧尾 Magic"),
|
||
expert_bad_length = ProtoField.string("fanuc10010.expert.bad_length", "长度异常"),
|
||
expert_bad_magic = ProtoField.string("fanuc10010.expert.bad_magic", "Magic 异常"),
|
||
}
|
||
|
||
for index = 1, POSE_COUNT do
|
||
fields["pose_" .. index] = ProtoField.float(
|
||
"fanuc10010.pose_" .. index,
|
||
"Pose " .. POSE_NAMES[index])
|
||
end
|
||
|
||
for index = 1, JOINT_COUNT do
|
||
fields["joint_" .. index] = ProtoField.float(
|
||
"fanuc10010.joint_" .. index,
|
||
JOINT_NAMES[index] .. " (raw)")
|
||
end
|
||
|
||
for index = 1, 6 do
|
||
fields["joint_deg_" .. index] = ProtoField.float(
|
||
"fanuc10010.joint_deg_" .. index,
|
||
JOINT_NAMES[index] .. " (deg)")
|
||
end
|
||
|
||
for index = 1, TAIL_COUNT do
|
||
fields["tail_" .. index] = ProtoField.uint32(
|
||
"fanuc10010.tail_" .. index,
|
||
"Tail[" .. (index - 1) .. "]",
|
||
base.DEC)
|
||
end
|
||
|
||
fanuc10010_proto.fields = fields
|
||
|
||
local function read_f32_be(tvb, offset)
|
||
return tvb(offset, 4):tvb():range(0, 4):float()
|
||
end
|
||
|
||
local function read_u32_be(tvb, offset)
|
||
return tvb(offset, 4):uint()
|
||
end
|
||
|
||
local function read_ascii(tvb, offset, length)
|
||
return tvb(offset, length):string()
|
||
end
|
||
|
||
local function radians_to_degrees(value)
|
||
return value * 180.0 / math.pi
|
||
end
|
||
|
||
local function add_error(subtree, range, field, message)
|
||
local item = subtree:add(field, range, message)
|
||
item:add_expert_info(PI_MALFORMED, PI_ERROR, message)
|
||
end
|
||
|
||
local function dissect_single_frame(tvb, pinfo, tree, frame_offset)
|
||
local remaining = tvb:len() - frame_offset
|
||
local candidate_length = math.min(remaining, FRAME_LENGTH)
|
||
local frame_range = tvb(frame_offset, candidate_length)
|
||
local subtree = tree:add(fanuc10010_proto, frame_range, "FANUC 10010 状态帧")
|
||
subtree:add(fields.frame, frame_range)
|
||
|
||
if remaining < FRAME_LENGTH then
|
||
add_error(subtree, frame_range, fields.expert_bad_length, "剩余字节不足 90B,无法组成完整状态帧")
|
||
return remaining
|
||
end
|
||
|
||
local header = read_ascii(tvb, frame_offset, HEADER_LENGTH)
|
||
local declared_length = read_u32_be(tvb, frame_offset + LENGTH_OFFSET)
|
||
local message_id = read_u32_be(tvb, frame_offset + MESSAGE_ID_OFFSET)
|
||
local trailer = read_ascii(tvb, frame_offset + FRAME_LENGTH - TRAILER_LENGTH, TRAILER_LENGTH)
|
||
|
||
subtree:add(fields.header, tvb(frame_offset, HEADER_LENGTH), header)
|
||
subtree:add(fields.declared_length, tvb(frame_offset + LENGTH_OFFSET, 4), declared_length)
|
||
subtree:add(fields.message_id, tvb(frame_offset + MESSAGE_ID_OFFSET, 4), message_id)
|
||
|
||
local pose_tree = subtree:add(fields.pose, tvb(frame_offset + POSE_OFFSET, POSE_COUNT * 4))
|
||
local pose_values = {}
|
||
for index = 1, POSE_COUNT do
|
||
local field_offset = frame_offset + POSE_OFFSET + ((index - 1) * 4)
|
||
local value = read_f32_be(tvb, field_offset)
|
||
pose_values[index] = value
|
||
pose_tree:add(fields["pose_" .. index], tvb(field_offset, 4), value)
|
||
end
|
||
|
||
local joint_tree = subtree:add(fields.joints, tvb(frame_offset + JOINT_OFFSET, JOINT_COUNT * 4))
|
||
local joint_values = {}
|
||
for index = 1, JOINT_COUNT do
|
||
local field_offset = frame_offset + JOINT_OFFSET + ((index - 1) * 4)
|
||
local value = read_f32_be(tvb, field_offset)
|
||
joint_values[index] = value
|
||
joint_tree:add(fields["joint_" .. index], tvb(field_offset, 4), value)
|
||
end
|
||
|
||
-- 单独保留一组角度显示,便于对照原始关节弧度值。
|
||
local joint_degree_tree = subtree:add(fields.joint_degrees, tvb(frame_offset + JOINT_OFFSET, 6 * 4))
|
||
for index = 1, 6 do
|
||
local field_offset = frame_offset + JOINT_OFFSET + ((index - 1) * 4)
|
||
joint_degree_tree:add(
|
||
fields["joint_deg_" .. index],
|
||
tvb(field_offset, 4),
|
||
radians_to_degrees(joint_values[index]))
|
||
end
|
||
|
||
local tail_tree = subtree:add(fields.tail, tvb(frame_offset + TAIL_OFFSET, TAIL_COUNT * 4))
|
||
local tail_values = {}
|
||
for index = 1, TAIL_COUNT do
|
||
local field_offset = frame_offset + TAIL_OFFSET + ((index - 1) * 4)
|
||
local value = read_u32_be(tvb, field_offset)
|
||
tail_values[index] = value
|
||
tail_tree:add(fields["tail_" .. index], tvb(field_offset, 4), value)
|
||
end
|
||
|
||
subtree:add(fields.trailer, tvb(frame_offset + FRAME_LENGTH - TRAILER_LENGTH, TRAILER_LENGTH), trailer)
|
||
|
||
if header ~= HEADER_MAGIC then
|
||
add_error(subtree, tvb(frame_offset, HEADER_LENGTH), fields.expert_bad_magic, "帧头不是 doz")
|
||
end
|
||
|
||
if trailer ~= TRAILER_MAGIC then
|
||
add_error(subtree, tvb(frame_offset + FRAME_LENGTH - TRAILER_LENGTH, TRAILER_LENGTH), fields.expert_bad_magic, "帧尾不是 zod")
|
||
end
|
||
|
||
if declared_length ~= FRAME_LENGTH then
|
||
add_error(subtree, tvb(frame_offset + LENGTH_OFFSET, 4), fields.expert_bad_length, "长度字段不是 90")
|
||
end
|
||
|
||
local summary = string.format(
|
||
"MsgId=%u X=%.3f Y=%.3f Z=%.3f J1=%.6f rad / %.3f deg J2=%.6f rad / %.3f deg Tail=[%u,%u,%u,%u]",
|
||
message_id,
|
||
pose_values[1], pose_values[2], pose_values[3],
|
||
joint_values[1], radians_to_degrees(joint_values[1]),
|
||
joint_values[2], radians_to_degrees(joint_values[2]),
|
||
tail_values[1], tail_values[2], tail_values[3], tail_values[4])
|
||
|
||
subtree:set_text("FANUC 10010 状态帧, " .. summary)
|
||
pinfo.cols.info:append(" | " .. summary)
|
||
|
||
return FRAME_LENGTH
|
||
end
|
||
|
||
function fanuc10010_proto.dissector(tvb, pinfo, tree)
|
||
if tvb:len() == 0 then
|
||
return 0
|
||
end
|
||
|
||
pinfo.cols.protocol = "FANUC10010"
|
||
|
||
local offset = 0
|
||
while offset < tvb:len() do
|
||
local consumed = dissect_single_frame(tvb, pinfo, tree, offset)
|
||
if consumed <= 0 then
|
||
break
|
||
end
|
||
|
||
offset = offset + consumed
|
||
if consumed < FRAME_LENGTH then
|
||
break
|
||
end
|
||
end
|
||
|
||
return tvb:len()
|
||
end
|
||
|
||
DissectorTable.get("tcp.port"):add(10010, fanuc10010_proto)
|