# FANUC Stream Motion 文档要点与实现差异

本文记录 `../FANUC_stream_motion.pdf` 中与本仓库 `Flyshot.Runtime.Fanuc` 直接相关的重点，并对照当前实现状态。

读取时间：2026-05-03

## 1. 文档定位

`FANUC_stream_motion.pdf` 对应 FANUC `Stream motion` 功能，选项号为 `A05B-2600-J519`。它描述的是外部设备通过以太网实时发送期望位置，让机器人按外部生成路径运动的控制方式。

文档明确要求外部设备自行生成满足机器人约束的路径，包括速度、加速度、jerk、可达性、姿态连续性等。FANUC 不提供完整运动学、逆解和碰撞检测公式。

## 2. 使用前提与示教程序

1. 机器人侧需要安装 J519 stream motion 选项。
2. 物理网口通过 `$STMO.$PHYS_PORT` 选择，`1` 表示 `CD38A`，`2` 表示 `CD38B`。
3. 机器人程序必须包含成对的 `IBGN start[*]` 和 `IBGN end[*]` 指令，二者编号必须一致，`start` 必须在 `end` 前一行。
4. `IBGN start[*]` 执行期间，机器人根据外部设备发来的期望位置运动；`IBGN end[*]` 之后程序继续执行。
5. 执行时要求 `AUTO` 模式和 `100% OVERRIDE`。

当前实现中的 `FanucControllerRuntime.EnableRobot()` 会按现场抓包流程启动 `RVBUSTSM` 程序，并随后允许 J519 在收到机器人 UDP status 包后回发命令。是否满足 `AUTO / 100% OVERRIDE / IBGN start` 已到位，当前只通过 J519 状态位和现场程序行为间接判断，没有在代码里读取或设置这些控制器状态。

## 3. UDP 60015 协议结构

协议使用 UDP，大端字节序，机器人侧端口为 `60015`。通信周期通常为 `8ms`，部分机型支持 `4ms`。状态包输出可以在任意时间通过 start/stop 控制包启停，不要求已经进入 `IBGN start[*]`。

### 3.1 状态输出 start 包

外部设备发给机器人：

| 字段 | 长度 | 值 |
| --- | --- | --- |
| Packet type | 4B | `0` |
| Version | 4B | `1` |

当前实现：`FanucJ519Protocol.PackInitPacket()` 已按 8B 大端控制包实现，`FanucJ519Client.ConnectAsync()` 连接后立即发送。

### 3.2 状态包

机器人发给外部设备，长度为 `132B`：

| 偏移 | 字段 | 含义 |
| --- | --- | --- |
| `0x00` | Packet type | `0` |
| `0x04` | Version | `1` |
| `0x08` | Sequence No. | 状态包序号，发送 start 包后从 `1` 重新开始 |
| `0x0c` | Status | bit0 接受命令、bit1 已收到命令、bit2 SYSRDY、bit3 运动中 |
| `0x0d..0x12` | Read I/O 回显和值 | 回显命令包中的读取 IO 类型、索引、掩码，并返回 16 点 IO 值 |
| `0x14` | Timestamp | ms 单位，2ms 分辨率 |
| `0x18..0x38` | Cartesian / external axis | `X/Y/Z/W/P/R` 加 3 个扩展轴 |
| `0x3c..0x5c` | Joint | `J1..J9`，单位 degree |
| `0x60..0x80` | Motor current | `J1..J9` 电流，单位 A |

当前实现：`FanucJ519Protocol.ParseResponse()` 已解析 `132B` 状态包，并暴露 `AcceptsCommand`、`ReceivedCommand`、`SystemReady`、`RobotInMotion` 四个状态位。`FanucControllerRuntime.GetSnapshot()` 也会把最新 J519 状态写进快照。

### 3.3 命令包

外部设备发给机器人，长度为 `64B`：

| 偏移 | 字段 | 含义 |
| --- | --- | --- |
| `0x00` | Packet type | `1` |
| `0x04` | Version | `1` |
| `0x08` | Sequence No. | 第一包应等于刚收到的状态包序号，后续逐包递增 |
| `0x0c` | Last data | 正常为 `0`；结束外部控制时最后一包设为 `1` |
| `0x0d..0x11` | Reading I/O | 可读取最多 16 个连续 IO 点 |
| `0x12` | Data format | `0` 笛卡尔，`1` 关节 |
| `0x13..0x19` | Writing I/O | 可写入最多 16 个连续 IO 点 |
| `0x1a` | unused | 2B |
| `0x1c..0x3c` | target[9] | 9 个 f32 目标值；关节格式时单位 degree |

当前实现：`FanucJ519Protocol.PackCommandPacket()` 已按上述布局打包，默认 `dataStyle=1`，也就是关节格式。运行时会把规划输出的弧度制关节轨迹转换为 degree 后下发。

### 3.4 状态输出 stop 包

外部设备发给机器人：

| 字段 | 长度 | 值 |
| --- | --- | --- |
| Packet type | 4B | `2` |
| Version | 4B | `1` |

文档把它定义为“停止状态包输出”的控制包，不是命令流正常终止的首选动作。命令流结束应通过 command packet 的 `Last data=1` 表达。

当前实现：`FanucJ519Client.StopMotionAsync()` 当前会停止状态包驱动发送并发送 packet type `2`，而稠密轨迹执行期间保持 `LastData=0`。这是与 FANUC 文档最明显的语义差异之一；已有多数 UTTC 抓包显示主运行窗口 `LastData=0`，但 `../j519 协议.pcap` 中存在 1 个 `LastData=1` 后紧跟 packet type `2` 的样本，后续应单独校准停止语义。

## 4. 通信时序重点

文档推荐的时序是：

1. 外部设备发送状态输出 start 包。
2. 机器人每个通信周期输出状态包。
3. 机器人程序执行到 `IBGN start[*]` 后，状态包 bit0 变为 `1`，表示等待命令包。
4. 外部设备收到 bit0 为 `1` 的状态包后，立即发送第一帧命令包，第一帧命令序号应等于刚收到的状态包序号。
5. 后续每收到一个状态包，外部设备应立即发送下一帧命令包。
6. 结束命令通信时，发送 `Last data=1` 的最后一帧命令包。

当前实现对照：

1. `FanucJ519Client` 已改为收到机器人 132B status 包后立即回发当前命令，不再由上位机本地固定 8ms 发送循环主动发包。
2. 命令包 sequence 已按刚收到的 status packet sequence 写入，避免第一帧从本地 `0` 起步。
3. `FanucControllerRuntime.ExecuteTrajectory()` 启动前会检查已有 J519 响应中的 `AcceptsCommand` 和 `SystemReady`；但如果还没收到状态包，则会放行，后续命令仍要等第一帧 status 到达才会发出。
4. 当前稠密轨迹结束不发送 `LastData=1`，而是依赖停止 J519 状态包驱动发送和 packet type `2` stop 控制包。

序号和节拍已经按手册方向校准；停止语义仍需在真实 R30iB 联调中继续确认。

## 5. 命令缓冲

文档说明机器人可以缓冲提前到达的 command packet。默认启用，缓冲上限为 `$STMO.$PKT_STACK - 1`，`$PKT_STACK` 默认 `10`，可配置范围 `2..10`。`$STMO.$START_MOVE` 决定积累多少未处理命令包后开始运动，默认 `1`。

注意事项：

1. 只有 command packet 会进入缓冲。
2. status output stop packet 会立即处理。
3. 如果 command buffer 中还有未处理包，不应发送 status output stop packet。
4. 使用 `Last data=1` 时，机器人会先处理完缓冲里的命令包，再结束外部控制。

当前实现没有显式预填 `$PKT_STACK` 缓冲，也没有读取 `$START_MOVE`。`FanucJ519Client` 只保存一个“当前命令”，由后台循环持续发送；`FanucControllerRuntime.SendDenseTrajectory()` 另一个 8ms 循环负责按轨迹时间更新这条当前命令。这与文档的“按状态包响应并可提前发多包缓冲”模型不同。

## 6. 可执行运动条件

文档列出的主要运动约束：

1. 目标点必须可达。
2. 笛卡尔格式下目标点对应的关节解必须唯一，且 configuration 要与 `IBGN start` 开始时一致。
3. 各轴必须满足上下限。
4. 不能发生自碰撞。
5. 必须考虑 FANUC J3 轴定义：J3 不是相对 J2 臂的夹角，而是机器人视角下相对水平面的 J3 臂角度。
6. 外部设备必须控制每轴速度、加速度、jerk 不超过 `$STMO_GRP` 下的限制。
7. 状态包中的当前位置是 servo feedback position，不是 command position。轨迹起点应平滑连接到机器人 command position，而不能简单用当前 servo position 直接起步。
8. reducer load 超限也会导致停机，负载相关计算不公开。

当前实现对这些条件的覆盖：

| 条件 | 当前状态 |
| --- | --- |
| 关节格式下发 | 已实现，当前现场链路默认只使用关节格式 |
| `rad -> deg` | 已实现，并由 UTTC J519 golden tests 覆盖 |
| `speed_ratio` 下发时间轴缩放 | 已实现，J519 实发时间为 `t_send = k * 0.008`，原轨迹采样时间为 `t_traj = t_send * speed_ratio` |
| IO 触发嵌入 J519 命令包 | 已实现，使用 `write_io_type/index/mask/value` |
| 速度、加速度、jerk 约束 | 规划层有 `acc_limit / jerk_limit` 等兼容参数，但未从 FANUC `$STMO_GRP` 在线读取，也未实现手册附录中的 20 档速度/负载插值 |
| J3 轴定义 | 当前文档未见专门处理；需要确认 `.robot` 模型与现场导出轨迹是否已经采用 FANUC J3 定义 |
| command position 起步 | `MoveJoint` 会用当前运行时记录的关节作为起点生成 PTP 稠密轨迹；但没有通过 FANUC HMI 通信读取 command position |
| reducer load | 未建模，依赖保守规划和现场报警反馈 |
| 笛卡尔格式限制 | 运行时不走笛卡尔 J519 目标格式，暂不覆盖 configuration 变化报警 |

## 7. 系统变量

与本仓库后续最相关的变量：

| 变量 | 默认/含义 |
| --- | --- |
| `$STMO.$PHYS_PORT` | 物理口，`1=CD38A`，`2=CD38B` |
| `$STMO.$COM_INT` | 通信周期，单位 ms，通常 `8`，只读 |
| `$STMO.$PKT_STACK` | command buffer 最大保留量，默认 `10` |
| `$STMO.$START_MOVE` | 缓冲中积累多少未处理命令后开始运动，默认 `1` |
| `$STMO_GRP.$JNT_VEL_LIM[*]` | 各轴速度上限，degree/s，只读 |
| `$STMO_GRP.$JNT_ACC_LIM[*]` | 各轴加速度上限，degree/s^2，只读 |
| `$STMO_GRP.$JNT_JRK_LIM[*]` | 各轴 jerk 上限，degree/s^3，只读 |
| `$STMO_GRP.$LMT_MODE` | 加速度/jerk 限制计算模式 |
| `$STMO_GRP.$WARN_LIM` | 接近限制时报警阈值，默认 `80%` |
| `$STMO_GRP.$FLTR_LN` | 命令目标移动平均滤波窗口 |
| `$STMO_GRP.$MAX_SPD` | 用于限制计算的 flange center 最大速度 |

当前实现没有读取或设置上述系统变量。`RobotProfile.ServoPeriod` 当前决定运行时发送周期；对当前现场而言应继续确认它与 `$COM_INT` 一致。

## 8. 附录 B：加速度和 jerk 限制

文档说明，在 `$STMO_GRP[].$LMT_MODE=0` 时，加速度和 jerk 的允许上限会根据 flange center speed 与 payload 计算：

1. 以 `$MAX_SPD` 分成 20 档速度区间。
2. 每个轴、每种限制类型都有无负载和最大负载两张 20 档表。
3. 实际 payload 通过线性插值得到限制表。
4. 实际 flange center speed 在相邻速度档之间线性插值。
5. 限制值不是每个通信周期都更新，而是在超过 `Vmax/20` 到再次跌回阈值的整段时间内，以该段观测到的 `Vpeak` 决定。
6. 如果长时间不跌回阈值，会按中间检查时间做临时判断。

文档还提供了 packet type `3` 的限制表查询协议：

| 包 | 方向 | 重点字段 |
| --- | --- | --- |
| 请求 | 外部设备 -> 机器人 | packet type `3`、version `1`、axis `1..9`、limit type `0=velocity/1=acceleration/2=jerk` |
| 响应 | 机器人 -> 外部设备 | packet type `3`、version `1`、axis、limit type、`Vmax`、中间检查时间、无负载 20 档、最大负载 20 档 |

当前实现没有 packet type `3` 查询，也没有实现手册描述的动态限制表算法。现阶段规划时长和保守程度主要依赖 replacement 自身参数与现场抓包对齐。

## 9. 报警与诊断

文档中与实现最相关的报警：

| 报警 | 含义 |
| --- | --- |
| `MOTN-600` | 命令序号与机器人期望不一致 |
| `MOTN-602` | data format 非法 |
| `MOTN-603` | 后续命令包未在通信周期内到达 |
| `MOTN-604` | 命令包过多，超出缓冲 |
| `MOTN-605` | 目标位置包含 NaN 或 infinity |
| `MOTN-606` | 非 AUTO 或 override 不是 100% |
| `MOTN-607` | 协议版本不匹配 |
| `MOTN-609/610/611` | 速度、加速度、jerk 超限 |
| `MOTN-612/613/614` | 接近速度、加速度、jerk 限制 |
| `MOTN-617` | 目标点与当前位置不连续 |
| `MOTN-619` | 当前机型不支持笛卡尔目标格式 |
| `PRIO-023` | 读写的 IO 类型或索引未分配 |

当前 `ControllerStateSnapshot.ActiveAlarms` 仍为空，Web 状态页也尚未接入 FANUC 报警列表。后续现场联调如果出现报警，应优先按上述表格关联 J519 包序号、目标数据、IO 字段、发送间隔和状态包 bit。

## 10. 与当前代码的结论

已基本对齐：

1. UDP 60015、大端、start/stop 控制包、64B command packet、132B status packet 的基础二进制布局。
2. `Data format=1` 的关节目标下发。
3. 状态位 bit0..bit3 的解析和快照暴露。
4. 规划输出 `rad` 转 J519 `deg`。
5. 根据 `speed_ratio` 做运行期时间轴缩放，而不是改变规划文件时间。
6. 飞拍 IO 触发通过 command packet 的写 IO 字段下发。
7. 命令发送按机器人 UDP status 包驱动，并使用最新 status sequence 回发。

主要差异/风险：

1. 当前未实现命令缓冲预填，也未读取 `$PKT_STACK / $START_MOVE`。
2. 当前停止运动依赖 packet type `2` stop 控制包，没有稳定发送 `LastData=1` 的最后 command packet；这与手册标准结束语义不同。
3. 当前未实现 packet type `3` 的速度/加速度/jerk 限制表查询，也未实现 payload/speed 20 档动态限制算法。
4. 当前没有自动校验 `AUTO / 100% OVERRIDE / brake control / resume offset / payload` 等控制器前置状态。
5. 当前没有报警码读取和 `MOTN-* / PRIO-*` 映射。

建议后续联调优先级：

1. 验证运动结束是否必须补 `LastData=1`；如果当前 stop 控制包能稳定工作，也应在文档中标为现场兼容路径，而不是手册标准路径。
2. 抓一次报警现场包，确认 `MOTN-600/603/617` 等是否能从包序号与状态位直接定位。
3. 如果后续追求更稳的真实机运行，补 packet type `3` 限制表查询，并把规划器的速度、加速度、jerk 校验与 FANUC 手册算法靠近。
