using System.Buffers.Binary; namespace Flyshot.Runtime.Fanuc.Protocol; /// /// 表示 FANUC TCP 10010 状态通道中的单个状态帧。 /// public sealed class FanucStateFrame { private readonly double[] _pose; private readonly double[] _jointOrExtensionValues; private readonly double[] _jointDegrees; private readonly double[] _externalAxes; private readonly uint[] _tailWords; /// /// 初始化状态帧解析结果。 /// /// 状态帧消息号或序号。 /// 控制器回传的笛卡尔位姿。 /// 控制器回传的关节或扩展轴状态。 /// 状态帧尾部状态槽位。 public FanucStateFrame( uint messageId, IEnumerable pose, IEnumerable jointOrExtensionValues, IEnumerable tailWords) { MessageId = messageId; _pose = pose?.ToArray() ?? throw new ArgumentNullException(nameof(pose)); _jointOrExtensionValues = jointOrExtensionValues?.ToArray() ?? throw new ArgumentNullException(nameof(jointOrExtensionValues)); _tailWords = tailWords?.ToArray() ?? throw new ArgumentNullException(nameof(tailWords)); if (_pose.Length != 6) { throw new ArgumentException("状态帧位姿必须包含 6 个 float。", nameof(pose)); } if (_jointOrExtensionValues.Length != 9) { throw new ArgumentException("状态帧关节/扩展轴必须包含 9 个 float。", nameof(jointOrExtensionValues)); } if (_tailWords.Length != 4) { throw new ArgumentException("状态帧尾部状态字必须包含 4 个 u32。", nameof(tailWords)); } _jointDegrees = _jointOrExtensionValues.Take(6).ToArray(); _externalAxes = _jointOrExtensionValues.Skip(6).ToArray(); } /// /// 获取状态帧消息号或序号。 /// public uint MessageId { get; } /// /// 获取控制器回传的笛卡尔位姿。 /// public IReadOnlyList Pose => _pose; /// /// 获取控制器回传的笛卡尔位姿 X/Y/Z/W/P/R,单位来自 FANUC 状态服务器。 /// public IReadOnlyList CartesianPose => _pose; /// /// 获取控制器回传的关节或扩展轴状态。 /// public IReadOnlyList JointOrExtensionValues => _jointOrExtensionValues; /// /// 获取前 6 个机器人关节角度,单位为度。 /// public IReadOnlyList JointDegrees => _jointDegrees; /// /// 获取后 3 个扩展轴槽位。当前现场样本中这些值通常为 0。 /// public IReadOnlyList ExternalAxes => _externalAxes; /// /// 获取状态帧尾部状态槽位。 /// public IReadOnlyList TailWords => _tailWords; /// /// 获取原始尾部状态字。当前抓包中恒为 [2,0,0,1],语义暂不强行推断。 /// public IReadOnlyList RawTailWords => _tailWords; /// /// 获取第 0 个原始尾部状态字。 /// public uint StatusWord0 => _tailWords[0]; /// /// 获取第 1 个原始尾部状态字。 /// public uint StatusWord1 => _tailWords[1]; /// /// 获取第 2 个原始尾部状态字。 /// public uint StatusWord2 => _tailWords[2]; /// /// 获取第 3 个原始尾部状态字。 /// public uint StatusWord3 => _tailWords[3]; } /// /// 提供 FANUC TCP 10010 状态通道固定帧解析能力。 /// public static class FanucStateProtocol { /// /// FANUC 状态通道抓包确认的完整帧长度。 /// public const int StateFrameLength = 90; /// /// 解析 TCP 10010 状态通道中的单个完整状态帧。 /// /// 完整状态帧。 /// 状态帧解析结果。 public static FanucStateFrame ParseFrame(ReadOnlySpan frame) { ValidateFrame(frame); var pose = new double[6]; var jointOrExtensionValues = new double[9]; var tailWords = new uint[4]; // 状态帧采用固定布局,偏移来自抓包与 StateServer 逆向结论。 for (var index = 0; index < pose.Length; index++) { pose[index] = BinaryPrimitives.ReadSingleBigEndian(frame.Slice(11 + (index * sizeof(float)), sizeof(float))); } for (var index = 0; index < jointOrExtensionValues.Length; index++) { jointOrExtensionValues[index] = BinaryPrimitives.ReadSingleBigEndian(frame.Slice(35 + (index * sizeof(float)), sizeof(float))); } for (var index = 0; index < tailWords.Length; index++) { tailWords[index] = BinaryPrimitives.ReadUInt32BigEndian(frame.Slice(71 + (index * sizeof(uint)), sizeof(uint))); } return new FanucStateFrame( BinaryPrimitives.ReadUInt32BigEndian(frame.Slice(7, sizeof(uint))), pose, jointOrExtensionValues, tailWords); } /// /// 校验状态帧的长度、magic 和长度字段。 /// /// 完整状态帧。 private static void ValidateFrame(ReadOnlySpan frame) { if (frame.Length != StateFrameLength) { throw new InvalidDataException("FANUC 状态帧长度不符合 TCP 10010 固定帧布局。"); } if (frame[0] != (byte)'d' || frame[1] != (byte)'o' || frame[2] != (byte)'z') { throw new InvalidDataException("FANUC 状态帧头 magic 不正确。"); } if (frame[^3] != (byte)'z' || frame[^2] != (byte)'o' || frame[^1] != (byte)'d') { throw new InvalidDataException("FANUC 状态帧尾 magic 不正确。"); } var declaredLength = BinaryPrimitives.ReadUInt32BigEndian(frame.Slice(3, sizeof(uint))); if (declaredLength != frame.Length) { throw new InvalidDataException("FANUC 状态帧长度字段与实际长度不一致。"); } } }