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)