diff --git a/README.md b/README.md index 5d0468e2..9da1699d 100644 --- a/README.md +++ b/README.md @@ -139,119 +139,16 @@ sudo script/ethercat/stop_ethercat.sh eno1 sudo script/ethercat/stop_ethercat.sh eno1 --restore-network ``` -## MotorService 与 Modbus TCP PLC +## 组件文档 -工程包含从 gRPC `MotorService`、`MotorManager`、`AbstractMotor` 到 -`ModbusTcpMotorBusRuntime` 的 CMVR PLC v1 电机控制链,x86-64 的 libmodbus -3.1.11 已放在 `dependency/x86/third_party/modbus/3.1.11`。 +具体能力、配置、协议和安全边界由对应代码目录下的 README 维护: -PLC 对接时特别注意: +- [MotorService gRPC 接口](cmvr-es/service/README.md#motorservice) +- [电机设备模块](cmvr-es/devices/motor/README.md) +- [Modbus TCP PLC runtime](cmvr-es/devices/motor/bus_runtime/modbus_tcp/README.md) +- [MotorService 与 CMVR PLC v1 完整协议](docs/motor_service_modbus_tcp.md) +- [AUBO 控制柜 Standard 数字 IO](cmvr-es/devices/arm/aubo_arm/README.md) +- [配置与部署规则](cmvr-es/config/README.md) -- `host` 必须配置为 IPv4 字面量,PLC boot ID 必须非零且每次重启变化; -- owner 决策、命令 ACK 都必须回显对应 session,重连不得执行旧 mailbox; -- PLC 在进程启动时可以离线;连接 supervisor 会继续退避重试,离线期间状态 - 返回不可用且运动命令不会写 mailbox; -- 状态区按 odd/even seqlock 发布,上位机使用 - sequence-before → 64-word block → sequence-after 三段读取验证; -- 每次 `OpenCyclicPosition/Velocity` 创建新 stream epoch,PLC 必须原子清零 - `last_applied_cyclic_sequence`、旧样本去重状态和 cyclic watchdog,确认 - `StreamActive` 与正确 mode 后才 ACK;重开后的首样本序列从 `1` 开始并必须 - 重新应用; -- 活动 cyclic 流跨 `connection_epoch` 后不会自动重开;旧流的当前和后续 - setpoint 均被拒绝并在 Quick Stop 后终止,客户端必须新建 gRPC 流。断链前 - 或断链期间 pending 的 setpoint 不会应用到新 session; -- 任何清理 Quick Stop 未确认时,MotorService 都会 fail-closed 锁存,并在 - 成功执行 `setEnabled(true)` 前拒绝新的运动命令; -- Modbus Quick Stop 只是功能性停止,不能替代硬接线急停或驱动器 STO。 -- 当前 Modbus 后端只提供 x86-64 的 libmodbus 3.1.11;`dependency/arm` - 尚无对应库,因此 ARM 构建暂不支持该后端。 - -完整 gRPC 语义、配置样例、寄存器表、TIA Portal 要求、构建测试和安全边界见 -[`docs/motor_service_modbus_tcp.md`](docs/motor_service_modbus_tcp.md)。 - -## AUBO 控制柜 IO - -`AuboArm` 通过通用的 `executeJsonCommand` 接口提供控制柜 Standard 数字 IO -读写。第一版支持以下命令: - -| `operation` | 说明 | 必填字段 | -| --- | --- | --- | -| `get_di` | 读取控制柜数字输入 | `index` | -| `get_do` | 读取控制柜数字输出及其 runstate | `index` | -| `set_do` | 设置控制柜数字输出 | `index`、`value` | - -JSON 命令示例: - -```json -{"command":"cabinet_io","operation":"get_di","index":0} -{"command":"cabinet_io","operation":"get_do","index":0} -{"command":"cabinet_io","operation":"set_do","index":0,"value":true} -``` - -其中 `index` 从 `0` 开始,运行时会根据控制器返回的 IO 数量检查范围。 -`set_do.value` 必须是 JSON 布尔值 `true` 或 `false`,不接受 `0/1` 或字符串。 -`set_do` 成功响应中的 `requested_value` 表示 SDK 已接受的请求值;需要确认控制器 -当前输出状态时,再调用一次 `get_do` 读取实际值。 - -成功响应示例: - -```json -{ - "success": true, - "command": "cabinet_io", - "operation": "get_di", - "index": 0, - "count": 16, - "value": false -} -``` - -### 通过 gRPC 调用 - -该功能复用 `cmvr.api.SystemService/ExecuteJsonCommand`。默认配置中的 gRPC -端口是 `50052`,读取 DI0: - -```shell -grpcurl -plaintext \ - -d '{ - "header":{"deviceId":"aubo_arm"}, - "requestJson":"{\"command\":\"cabinet_io\",\"operation\":\"get_di\",\"index\":0}" - }' \ - 127.0.0.1:50052 \ - cmvr.api.SystemService/ExecuteJsonCommand -``` - -设置 DO0 为高电平: - -```shell -grpcurl -plaintext \ - -d '{ - "header":{"deviceId":"aubo_arm"}, - "requestJson":"{\"command\":\"cabinet_io\",\"operation\":\"set_do\",\"index\":0,\"value\":true}" - }' \ - 127.0.0.1:50052 \ - cmvr.api.SystemService/ExecuteJsonCommand -``` - -使用源码树默认配置时,先在 `cmvr-es/config/manager/device_manager.pb.txt` 中把 -`aubo_arm` 的 `enable` 改为 `true`,并在 -`cmvr-es/config/devices/arm/aubo_arm.pb.txt` 中配置正确的控制器地址和登录信息, -然后重新安装配置并启动安装产物: - -```shell -cmake --install build -./output/bin/cmvr_es -``` - -`output/bin/cmvr_es` 读取的是 `output/bin/config/`;如果进程使用显式配置路径, -应修改该配置根下的对应文件。 -设备未启用或初始化失败时,gRPC 会返回 `Device not found: aubo_arm`。 - -### 安全约束 - -- 该接口只访问控制柜 Standard 数字 IO,不操作工具端 IO、可配置 IO 或安全 IO。 -- `set_do` 不会修改控制器的输出 runstate。只有目标通道的 runstate 为 - `StandardOutputRunState::None` 时才允许写入,否则返回 - `output_managed_by_runstate`。 -- 接口不会调用会重置全部输出配置的 `setDigitalOutputRunstateDefault()`。 -- 模拟量 IO 涉及 domain、单位和量程,第一版暂不通过该 JSON 接口开放。 +Modbus Quick Stop 只是功能性停止,不能替代硬接线急停或驱动器 STO。AUBO +JSON 接口只访问控制柜 Standard 数字 IO,不访问安全 IO。 diff --git a/cmvr-es/devices/README.md b/cmvr-es/devices/README.md index aaf43066..1fa6ac7c 100644 --- a/cmvr-es/devices/README.md +++ b/cmvr-es/devices/README.md @@ -37,12 +37,12 @@ config/cmvr_es.pb.txt | --- | --- | --- | --- | | Camera | [`camera/abstract_camera.h`](camera/abstract_camera.h) | [`camera/camera_factory.h`](camera/camera_factory.h) | UVC、RealSense、Hikvision | | AGV | [`agv/abstract_agv.h`](agv/abstract_agv.h) | [`agv/agv_factory.h`](agv/agv_factory.h) | MyAgv、SRC1100 | -| RobotArm | [`arm/robot_arm.h`](arm/robot_arm.h) | [`arm/robot_arm_factory.h`](arm/robot_arm_factory.h) | MotorRobotArm、AUBO、Huayan | +| RobotArm | [`arm/robot_arm.h`](arm/robot_arm.h) | [`arm/robot_arm_factory.h`](arm/robot_arm_factory.h) | MotorRobotArm、[AUBO](arm/aubo_arm/README.md)、Huayan、UME | | DexHand | [`dexhand/abstract_dexhand.h`](dexhand/abstract_dexhand.h) | [`dexhand/dexhand_factory.h`](dexhand/dexhand_factory.h) | RH56DFTP、PX6AXGen3 | | Microphone | [`microphone/abstract_microphone.h`](microphone/abstract_microphone.h) | [`microphone/microphone_factory.h`](microphone/microphone_factory.h) | FFmpeg | | Speaker | [`speaker/abstract_speaker.h`](speaker/abstract_speaker.h) | [`speaker/speaker_factory.h`](speaker/speaker_factory.h) | FFmpeg | | BioHead | [`biohead/abstract_biohead.h`](biohead/abstract_biohead.h) | DeviceFactory 直接创建 | BioHeadRobot | -| MotorSystem | `motor/motor_system/` | DeviceFactory 直接创建 | CAN/MuJoCo motor group | +| MotorSystem | [`motor/`](motor/README.md) | DeviceFactory 直接创建 | CAN、MuJoCo、EtherCAT、Modbus TCP PLC | 代码目录存在不等于已经接入配置创建链: diff --git a/cmvr-es/devices/arm/aubo_arm/README.md b/cmvr-es/devices/arm/aubo_arm/README.md new file mode 100644 index 00000000..6d63e6b7 --- /dev/null +++ b/cmvr-es/devices/arm/aubo_arm/README.md @@ -0,0 +1,124 @@ +# AUBO RobotArm 与控制柜 IO + +`AuboArm` 是 AUBO SDK v0.27.1 的 `RobotArm` 后端。控制柜 Standard 数字 IO +通过设备通用的 `executeJsonCommand` 接口访问,远程调用复用 +`cmvr.api.SystemService/ExecuteJsonCommand`,不经过 `ArmService` 或 +`MotorService`。 + +返回 [Devices 模块指南](../../README.md) 或 [项目总览](../../../../README.md)。 + +## 代码与配置 + +- 实现:[`aubo_arm.h`](aubo_arm.h)、[`aubo_arm.cpp`](aubo_arm.cpp) +- 测试:[`tests/aubo_arm_json_command_test.cpp`](tests/aubo_arm_json_command_test.cpp) +- 设备配置:[`../../../config/devices/arm/aubo_arm.pb.txt`](../../../config/devices/arm/aubo_arm.pb.txt) +- DeviceManager 配置: + [`../../../config/manager/device_manager.pb.txt`](../../../config/manager/device_manager.pb.txt) +- SystemService 实现: + [`../../../service/grpc/src/grpc_system_service.cpp`](../../../service/grpc/src/grpc_system_service.cpp) +- Proto:[`../../../../protos/cmvr/api/system_service.proto`](../../../../protos/cmvr/api/system_service.proto) + +仓库配置使用 SDK RPC 端口 `30004`。现场部署必须填写真实控制器地址和凭据, +不要把生产密码提交到默认配置。 + +## 控制柜 Standard 数字 IO + +当前支持: + +| `operation` | 说明 | 必填字段 | +| --- | --- | --- | +| `get_di` | 读取控制柜数字输入 | `index` | +| `get_do` | 读取控制柜数字输出及其 runstate | `index` | +| `set_do` | 设置控制柜数字输出 | `index`、`value` | + +JSON 命令: + +```json +{"command":"cabinet_io","operation":"get_di","index":0} +{"command":"cabinet_io","operation":"get_do","index":0} +{"command":"cabinet_io","operation":"set_do","index":0,"value":true} +``` + +`index` 从 `0` 开始,运行时根据控制器返回的 IO 数量检查范围。 +`set_do.value` 必须是 JSON 布尔值 `true` 或 `false`,不接受 `0/1` 或字符串。 +`set_do` 成功响应中的 `requested_value` 只表示 SDK 已接受请求;确认实际输出时 +必须再调用 `get_do`。 + +读取成功响应示例: + +```json +{ + "success": true, + "command": "cabinet_io", + "operation": "get_di", + "index": 0, + "count": 16, + "value": false +} +``` + +## 通过 gRPC 调用 + +默认 gRPC 端口为 `50052`。读取 DI0: + +```shell +grpcurl -plaintext \ + -d '{ + "header":{"deviceId":"aubo_arm"}, + "requestJson":"{\"command\":\"cabinet_io\",\"operation\":\"get_di\",\"index\":0}" + }' \ + 127.0.0.1:50052 \ + cmvr.api.SystemService/ExecuteJsonCommand +``` + +设置 DO0 为高电平: + +```shell +grpcurl -plaintext \ + -d '{ + "header":{"deviceId":"aubo_arm"}, + "requestJson":"{\"command\":\"cabinet_io\",\"operation\":\"set_do\",\"index\":0,\"value\":true}" + }' \ + 127.0.0.1:50052 \ + cmvr.api.SystemService/ExecuteJsonCommand +``` + +使用源码默认配置时: + +1. 在 `cmvr-es/config/devices/arm/aubo_arm.pb.txt` 填写正确地址和登录信息; +2. 在 `cmvr-es/config/manager/device_manager.pb.txt` 将 `aubo_arm.enable` + 改为 `true`; +3. 重新安装配置并启动安装产物。 + +```shell +cmake --install build +./output/bin/cmvr_es +``` + +`output/bin/cmvr_es` 默认读取 `output/bin/config/`。使用 `--config` 时,应修改 +对应外部配置根。设备未启用或初始化失败时,gRPC 返回 +`Device not found: aubo_arm`。 + +## 安全与语义边界 + +- 只访问控制柜 Standard 数字 IO,不访问工具端 IO、可配置 IO 或安全 IO; +- `set_do` 不修改输出 runstate; +- 只有 `StandardOutputRunState::None` 的通道允许写入,否则返回 + `output_managed_by_runstate`; +- 普通访问不会调用会重置全部输出配置的 + `setDigitalOutputRunstateDefault()`; +- 模拟量 IO 涉及 domain、单位和量程,当前 JSON 接口不开放; +- gRPC/JSON 返回成功不代表目标 IO 具备功能安全等级; +- 真实写测试前应确认通道用途、负载、电气隔离、默认电平和控制器程序所有权。 + +## 测试 + +```bash +cmake --build build --target aubo_arm_json_command_test -j4 +ctest --test-dir build \ + -R '^aubo_arm_json_command_test$' \ + --output-on-failure +``` + +该测试覆盖 JSON 校验和无硬件错误路径,不代表已在真实 AUBO 控制柜完成 DI/DO +读取、写入或 runstate 拒绝验证。 diff --git a/cmvr-es/devices/motor/README.md b/cmvr-es/devices/motor/README.md new file mode 100644 index 00000000..a10ba7fc --- /dev/null +++ b/cmvr-es/devices/motor/README.md @@ -0,0 +1,82 @@ +# Motor 设备模块 + +`devices/motor/` 提供电机管理、协议适配、总线 runtime 和厂商驱动。Service、 +RobotArm 和业务 Task 只依赖 `MotorManager`/`AbstractMotor` 的稳定接口,不应 +直接访问 libmodbus、CAN、EtherCAT 或厂商 SDK。 + +返回 [Devices 模块指南](../README.md) 或 [项目总览](../../../README.md)。 + +## 目录职责 + +| 目录 | 职责 | +| --- | --- | +| `manager/` | 创建 MotorGroup,按 `motor_id`/`joint_name` 暴露 `AbstractMotor` | +| `bus_runtime/` | 连接、收发、重连、watchdog 和总线生命周期 | +| `drivers/` | CANopen、EtherCAT、MuJoCo、Modbus PLC 等具体后端 | +| `drivers/modbus_plc_motor/` | 关节限位、SI 单位和 CMVR PLC v1 命令映射 | + +PLC 电机链路为: + +```text +gRPC MotorService + | + v +MotorManager -> AbstractMotor + | + v +CmvrPlcMotorProtocol + | + v +ModbusTcpMotorBusRuntime + | + v +libmodbus -> PLC -> 驱动器/电机 +``` + +各层边界: + +- `MotorService` 负责 API 校验、单电机控制权、deadline/cancellation 和 + fail-closed Quick Stop; +- `MotorManager` 负责电机查找与统一抽象; +- `CmvrPlcMotorProtocol` 负责关节限位、SI 单位和 CMVR PLC v1 命令映射; +- `ModbusTcpMotorBusRuntime` 负责 PLC session、mailbox、ACK、状态和重连; +- PLC/驱动器必须独立实现通信 watchdog、周期 watchdog 和硬件安全动作。 + +## Modbus TCP PLC + +实现、配置、依赖和测试入口见: + +- [Modbus TCP runtime README](bus_runtime/modbus_tcp/README.md) +- [完整 MotorService/CMVR PLC v1 协议](../../../docs/motor_service_modbus_tcp.md) +- [MotorService 文档](../../service/README.md#motorservice) +- [`plc_motors.pb.txt`](../../config/devices/motor/plc_motors.pb.txt) + +当前 Modbus 后端只提供 x86-64 的 libmodbus 3.1.11。ARM 目录没有对应库, +不能把 x86 ELF 复制到 ARM 设备使用。 + +## 安全边界 + +- MotorService 是单轴 API,不提供多轴同扫描周期的原子 commit; +- PLC/Modbus 的 Profile 或 cyclic 能力不能直接当成毫秒级机械臂组伺服; +- 软件 `emergencyStop`、Quick Stop 和普通 PLC 输出都不是安全急停; +- 真实设备必须具有独立的硬接线急停、安全继电器或 F-CPU/F-I/O,以及驱动器 + STO 等经风险评估确定的安全链; +- 新硬件配置保持 `enable: false`,完成方向、限位、watchdog 和故障注入验证后 + 才能启用。 + +## 测试 + +```bash +cmake --build build --target \ + modbus_tcp_motor_bus_runtime_test \ + grpc_motor_service_test \ + grpc_motor_service_modbus_e2e_test \ + -j4 + +ctest --test-dir build \ + -R '^(modbus_tcp_motor_bus_runtime_test|grpc_motor_service_test|grpc_motor_service_modbus_e2e_test)$' \ + --output-on-failure +``` + +端到端 fake PLC 测试需要本地 TCP bind/listen 权限。软件测试不能代替真实 +S7-1215C、驱动器、STO 和断网故障台架。 diff --git a/cmvr-es/devices/motor/bus_runtime/modbus_tcp/README.md b/cmvr-es/devices/motor/bus_runtime/modbus_tcp/README.md new file mode 100644 index 00000000..f1ac4f18 --- /dev/null +++ b/cmvr-es/devices/motor/bus_runtime/modbus_tcp/README.md @@ -0,0 +1,93 @@ +# Modbus TCP PLC Runtime + +本目录实现 CMVR PLC v1 的 Modbus TCP 总线 runtime。它负责 PLC 连接、身份 +握手、session、心跳、重连、命令 mailbox、ACK 轮询和状态快照,不负责 gRPC +请求解析,也不提供功能安全急停。 + +返回 [Motor 设备模块](../../README.md) 或 [Devices 模块指南](../../../README.md)。 + +## 代码与配置 + +- [`include/modbus_tcp_client.h`](include/modbus_tcp_client.h):有界超时的 + libmodbus client +- [`include/modbus_tcp_motor_bus_runtime.h`](include/modbus_tcp_motor_bus_runtime.h): + 连接 supervisor、命令和状态 runtime +- [`include/cmvr_plc_register_map.h`](include/cmvr_plc_register_map.h): + CMVR PLC v1 寄存器常量 +- [`src/modbus_tcp_client.cpp`](src/modbus_tcp_client.cpp) +- [`src/modbus_tcp_motor_bus_runtime.cpp`](src/modbus_tcp_motor_bus_runtime.cpp) +- [`tests/modbus_tcp_motor_bus_runtime_test.cpp`](tests/modbus_tcp_motor_bus_runtime_test.cpp) +- [`../../../../config/devices/motor/plc_motors.pb.txt`](../../../../config/devices/motor/plc_motors.pb.txt) + +完整寄存器表、TIA Portal 数据块、gRPC 语义和台架步骤由 +[MotorService 与 CMVR PLC v1 完整协议](../../../../../docs/motor_service_modbus_tcp.md) +统一维护。 + +## 连接与协议约束 + +- `host` 必须是 IPv4 字面量,避免 DNS 让建连或停止出现无界等待; +- PLC boot ID 必须非零且每次 PLC 重启变化; +- owner 决策和命令 ACK 必须回显当前 session,旧 session 的 mailbox 不得执行; +- runtime `start()` 启动连接 supervisor,PLC 可以在进程启动时离线; +- 离线期间状态不可用,运动命令必须在写 mailbox 前失败; +- 重连必须重新完成身份、版本、boot ID 和 session 握手,不得重放旧命令; +- 状态区按 odd/even seqlock 发布,CMVR 使用 + sequence-before → 64-word block → sequence-after 三段读取; +- payload 必须先完整写入,commit sequence 最后写入,PLC 只原子消费新的 + commit; +- Quick Stop、Disable、故障和通信 watchdog 的状态必须通过当前 session + 的 ACK/状态确认,不能把本地写成功解释为驱动器已经安全停止。 + +## Cyclic stream + +每次 `OpenCyclicPosition/Velocity` 都创建新的轴级 stream epoch。PLC 必须在 +同一个原子状态事务中: + +1. 清零 `last_applied_cyclic_sequence`; +2. 清理旧样本去重状态; +3. 重置 cyclic watchdog; +4. 设置正确的 CSP/CSV mode; +5. 发布 `StreamActive=1` 后再 ACK。 + +重开后的首个样本序列从 `1` 开始,必须重新应用。活动 cyclic 流跨 +`connection_epoch` 后不会自动重开;旧流的当前和后续 setpoint 都被拒绝, +Quick Stop 后客户端必须建立新的 gRPC 流。断链前或断链期间 pending 的 +setpoint 不得进入新 session。 + +## 依赖与平台 + +x86-64 的 libmodbus 3.1.11 位于: + +```text +dependency/x86/third_party/modbus/3.1.11 +``` + +`dependency/arm/third_party/` 当前没有对应 libmodbus,因此 ARM 构建不支持 +该后端。支持 ARM 前必须为目标 ABI 单独编译并验证库,不能复用 x86 二进制。 + +## 安全边界 + +标准 S7-1215C DC/DC/DC 不是 failsafe PLC。Modbus Quick Stop、 +MotorService `emergencyStop`、普通 OB/FB 和普通数字输出都只是功能性控制, +不能替代: + +- 硬接线急停; +- 安全继电器或 F-CPU/F-I/O; +- 驱动器双通道 STO; +- 接触器、抱闸反馈与必要的 EDM。 + +PLC 侧通信 watchdog 和 cyclic watchdog 必须在没有 CMVR 进程参与时独立停止 +危险运动。真实启用前必须在禁能或脱载轴上验证寄存器、方向、限位、断网、PLC +重启、交换机故障和 Quick Stop 失败。 + +## 测试 + +```bash +cmake --build build --target modbus_tcp_motor_bus_runtime_test -j4 +ctest --test-dir build \ + -R '^modbus_tcp_motor_bus_runtime_test$' \ + --output-on-failure +``` + +fake PLC 测试需要本地 TCP bind/listen 权限。测试通过只证明软件协议和故障注入 +路径,不代表真实 PLC、驱动器或硬件安全链已经验收。 diff --git a/cmvr-es/service/README.md b/cmvr-es/service/README.md index 0ed71894..59959741 100644 --- a/cmvr-es/service/README.md +++ b/cmvr-es/service/README.md @@ -21,6 +21,42 @@ gRPC 和 QUIC 的职责边界: - 实时音视频使用 QUIC DATAGRAM; - `quic_edge/` 不是平台 Gateway,也不是浏览器服务器。 +## MotorService + +`MotorService` 将 gRPC 电机命令适配到已经由 `DeviceManager` 创建的 +`MotorManager` 和 `AbstractMotor`,不直接持有 PLC、现场总线或厂商驱动。 + +关键文件: + +- Proto:[`../../protos/cmvr/api/motor_service.proto`](../../protos/cmvr/api/motor_service.proto) + 和 [`../../protos/cmvr/api/motor_command.proto`](../../protos/cmvr/api/motor_command.proto) +- 实现:[`grpc/include/grpc_motor_service.h`](grpc/include/grpc_motor_service.h) + 和 [`grpc/src/grpc_motor_service.cpp`](grpc/src/grpc_motor_service.cpp) +- 注册:[`../task/grpc_server_task/src/grpc_server_task.cpp`](../task/grpc_server_task/src/grpc_server_task.cpp) +- 单元测试:[`grpc/tests/grpc_motor_service_test.cpp`](grpc/tests/grpc_motor_service_test.cpp) +- gRPC–Modbus 端到端测试: + [`grpc/tests/grpc_motor_service_modbus_e2e_test.cpp`](grpc/tests/grpc_motor_service_modbus_e2e_test.cpp) + +服务按单电机仲裁。同步 Profile 命令、Cyclic Position/Velocity 双向流、 +`setEnabled`、状态读取和软件 `emergencyStop` 共用同一控制权状态: + +- 同一电机已有 owner 时拒绝新的控制调用; +- cyclic 流首帧必须是 `open`,后续 setpoint sequence 必须严格递增; +- reader 使用 latest-wins 邮箱,客户端必须持续并发读取反馈; +- 取消、deadline、watchdog、非法帧、后端拒绝或写失败都会触发 Quick Stop; +- 任何清理 Quick Stop 未确认时,服务进入 fail-closed 锁存; +- 只有成功执行 `setEnabled(true)` 才解除服务内软件急停锁存; +- 服务层 Quick Stop 和 `emergencyStop` 都不具备功能安全等级。 + +PLC 后端的连接 epoch、stream epoch、寄存器、ACK 和 TIA Portal 要求见: + +- [Modbus TCP PLC runtime](../devices/motor/bus_runtime/modbus_tcp/README.md) +- [MotorService 与 CMVR PLC v1 完整协议](../../docs/motor_service_modbus_tcp.md) + +AUBO 控制柜 IO 不经过 `MotorService` 或 `ArmService`,而是复用 +`SystemService/ExecuteJsonCommand`。厂商命令和安全约束见 +[AUBO 控制柜 IO](../devices/arm/aubo_arm/README.md)。 + ## 新增 gRPC Service 当前没有动态 service registry,必须完成以下全部步骤。