using System.Buffers.Binary; namespace Flyshot.Runtime.Fanuc.Protocol; /// /// 表示 FANUC UDP 60015 J519/ICSP 伺服流中的一帧命令数据。 /// public sealed class FanucJ519Command { private readonly double[] _targetJoints; /// /// 初始化 J519 命令数据。 /// /// 命令序号。 /// 目标关节或扩展轴数据,最多 9 个槽位。 /// 是否为最后一帧数据。 /// 读取 IO 类型。 /// 读取 IO 起始索引。 /// 读取 IO 掩码。 /// 目标数据类型。 /// 写入 IO 类型。 /// 写入 IO 起始索引。 /// 写入 IO 掩码。 /// 写入 IO 数值。 public FanucJ519Command( uint sequence, IReadOnlyList targetJoints, byte lastData = 0, byte readIoType = 2, ushort readIoIndex = 1, ushort readIoMask = 255, byte dataStyle = 1, byte writeIoType = 2, ushort writeIoIndex = 1, ushort writeIoMask = 0, ushort writeIoValue = 0) { ArgumentNullException.ThrowIfNull(targetJoints); if (targetJoints.Count is <= 0 or > 9) { throw new ArgumentOutOfRangeException(nameof(targetJoints), "J519 目标数据必须包含 1 到 9 个槽位。"); } Sequence = sequence; LastData = lastData; ReadIoType = readIoType; ReadIoIndex = readIoIndex; ReadIoMask = readIoMask; DataStyle = dataStyle; WriteIoType = writeIoType; WriteIoIndex = writeIoIndex; WriteIoMask = writeIoMask; WriteIoValue = writeIoValue; _targetJoints = targetJoints.ToArray(); } /// /// 获取命令序号。 /// public uint Sequence { get; } /// /// 获取是否为最后一帧数据。 /// public byte LastData { get; } /// /// 获取读取 IO 类型。 /// public byte ReadIoType { get; } /// /// 获取读取 IO 起始索引。 /// public ushort ReadIoIndex { get; } /// /// 获取读取 IO 掩码。 /// public ushort ReadIoMask { get; } /// /// 获取目标数据类型。 /// public byte DataStyle { get; } /// /// 获取写入 IO 类型。 /// public byte WriteIoType { get; } /// /// 获取写入 IO 起始索引。 /// public ushort WriteIoIndex { get; } /// /// 获取写入 IO 掩码。 /// public ushort WriteIoMask { get; } /// /// 获取写入 IO 数值。 /// public ushort WriteIoValue { get; } /// /// 获取目标关节或扩展轴数据。 /// public IReadOnlyList TargetJoints => _targetJoints; } /// /// 表示 FANUC UDP 60015 J519/ICSP 伺服流中的一帧响应数据。 /// public sealed class FanucJ519Response { private readonly double[] _pose; private readonly double[] _externalAxes; private readonly double[] _jointDegrees; private readonly double[] _motorCurrents; /// /// 初始化 J519 响应数据。 /// /// 响应类型。 /// 协议版本。 /// 响应序号。 /// 状态位集合。 /// 读取 IO 类型。 /// 读取 IO 起始索引。 /// 读取 IO 掩码。 /// 读取 IO 数值。 /// 控制器时间戳。 /// TCP 笛卡尔位姿。 /// 扩展轴反馈。 /// 关节角度反馈。 /// 电机电流反馈。 public FanucJ519Response( uint messageType, uint version, uint sequence, byte status, byte readIoType, ushort readIoIndex, ushort readIoMask, ushort readIoValue, uint timestamp, IEnumerable pose, IEnumerable externalAxes, IEnumerable jointDegrees, IEnumerable motorCurrents) { MessageType = messageType; Version = version; Sequence = sequence; Status = status; ReadIoType = readIoType; ReadIoIndex = readIoIndex; ReadIoMask = readIoMask; ReadIoValue = readIoValue; Timestamp = timestamp; _pose = pose?.ToArray() ?? throw new ArgumentNullException(nameof(pose)); _externalAxes = externalAxes?.ToArray() ?? throw new ArgumentNullException(nameof(externalAxes)); _jointDegrees = jointDegrees?.ToArray() ?? throw new ArgumentNullException(nameof(jointDegrees)); _motorCurrents = motorCurrents?.ToArray() ?? throw new ArgumentNullException(nameof(motorCurrents)); } /// /// 获取响应类型。 /// public uint MessageType { get; } /// /// 获取协议版本。 /// public uint Version { get; } /// /// 获取响应序号。 /// public uint Sequence { get; } /// /// 获取状态位集合。 /// public byte Status { get; } /// /// 获取读取 IO 类型。 /// public byte ReadIoType { get; } /// /// 获取读取 IO 起始索引。 /// public ushort ReadIoIndex { get; } /// /// 获取读取 IO 掩码。 /// public ushort ReadIoMask { get; } /// /// 获取读取 IO 数值。 /// public ushort ReadIoValue { get; } /// /// 获取控制器时间戳。 /// public uint Timestamp { get; } /// /// 获取 TCP 笛卡尔位姿。 /// public IReadOnlyList Pose => _pose; /// /// 获取扩展轴反馈。 /// public IReadOnlyList ExternalAxes => _externalAxes; /// /// 获取关节角度反馈。 /// public IReadOnlyList JointDegrees => _jointDegrees; /// /// 获取电机电流反馈。 /// public IReadOnlyList MotorCurrents => _motorCurrents; /// /// 获取控制器是否接受命令。 /// public bool AcceptsCommand => (Status & 0b0001) != 0; /// /// 获取控制器是否已收到命令。 /// public bool ReceivedCommand => (Status & 0b0010) != 0; /// /// 获取控制器系统是否就绪。 /// public bool SystemReady => (Status & 0b0100) != 0; /// /// 获取机器人是否处于运动中。 /// public bool RobotInMotion => (Status & 0b1000) != 0; } /// /// 提供 FANUC UDP 60015 J519/ICSP 伺服流的基础封包与响应解析能力。 /// public static class FanucJ519Protocol { /// /// J519 初始化和结束控制包长度。 /// public const int ControlPacketLength = 8; /// /// J519 命令包长度。 /// public const int CommandPacketLength = 64; /// /// J519 响应包长度。 /// public const int ResponsePacketLength = 132; /// /// 封装 J519 初始化包。 /// /// 初始化包。 public static byte[] PackInitPacket() { return PackControlPacket(0); } /// /// 封装 J519 结束包。 /// /// 结束包。 public static byte[] PackEndPacket() { return PackControlPacket(2); } /// /// 封装 J519 64 字节命令包。 /// /// 命令数据。 /// 命令包。 public static byte[] PackCommandPacket(FanucJ519Command command) { ArgumentNullException.ThrowIfNull(command); var packet = new byte[CommandPacketLength]; BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0x00, sizeof(uint)), 1); BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0x04, sizeof(uint)), 1); BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0x08, sizeof(uint)), command.Sequence); packet[0x0c] = command.LastData; packet[0x0d] = command.ReadIoType; BinaryPrimitives.WriteUInt16BigEndian(packet.AsSpan(0x0e, sizeof(ushort)), command.ReadIoIndex); BinaryPrimitives.WriteUInt16BigEndian(packet.AsSpan(0x10, sizeof(ushort)), command.ReadIoMask); packet[0x12] = command.DataStyle; packet[0x13] = command.WriteIoType; BinaryPrimitives.WriteUInt16BigEndian(packet.AsSpan(0x14, sizeof(ushort)), command.WriteIoIndex); BinaryPrimitives.WriteUInt16BigEndian(packet.AsSpan(0x16, sizeof(ushort)), command.WriteIoMask); BinaryPrimitives.WriteUInt16BigEndian(packet.AsSpan(0x18, sizeof(ushort)), command.WriteIoValue); BinaryPrimitives.WriteUInt16BigEndian(packet.AsSpan(0x1a, sizeof(ushort)), 0); // J519 命令包固定保留 9 个 f32 目标槽位,少于 9 个时剩余槽位补零。 for (var index = 0; index < 9; index++) { var value = index < command.TargetJoints.Count ? command.TargetJoints[index] : 0.0; BinaryPrimitives.WriteSingleBigEndian(packet.AsSpan(0x1c + (index * sizeof(float)), sizeof(float)), (float)value); } return packet; } /// /// 解析 J519 132 字节响应包。 /// /// 响应包。 /// 响应解析结果。 public static FanucJ519Response ParseResponse(ReadOnlySpan packet) { if (packet.Length != ResponsePacketLength) { throw new InvalidDataException("FANUC J519 响应包长度不正确。"); } return new FanucJ519Response( BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(0x00, sizeof(uint))), BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(0x04, sizeof(uint))), BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(0x08, sizeof(uint))), packet[0x0c], packet[0x0d], BinaryPrimitives.ReadUInt16BigEndian(packet.Slice(0x0e, sizeof(ushort))), BinaryPrimitives.ReadUInt16BigEndian(packet.Slice(0x10, sizeof(ushort))), BinaryPrimitives.ReadUInt16BigEndian(packet.Slice(0x12, sizeof(ushort))), BinaryPrimitives.ReadUInt32BigEndian(packet.Slice(0x14, sizeof(uint))), ReadFloatArray(packet, 0x18, 6), ReadFloatArray(packet, 0x30, 3), ReadFloatArray(packet, 0x3c, 9), ReadFloatArray(packet, 0x60, 9)); } /// /// 封装 J519 控制包。 /// /// 控制包类型。 /// 控制包。 private static byte[] PackControlPacket(uint packetType) { var packet = new byte[ControlPacketLength]; BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0, sizeof(uint)), packetType); BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(sizeof(uint), sizeof(uint)), 1); return packet; } /// /// 从响应包中读取固定长度 f32 数组。 /// /// 响应包。 /// 数组起始偏移。 /// 数组元素数量。 /// 转换成 double 的数值数组。 private static double[] ReadFloatArray(ReadOnlySpan packet, int offset, int count) { var values = new double[count]; for (var index = 0; index < count; index++) { values[index] = BinaryPrimitives.ReadSingleBigEndian(packet.Slice(offset + (index * sizeof(float)), sizeof(float))); } return values; } }