Compare commits

..

No commits in common. "edb01463ff8aea869ca39778ea4f006ba674cd44" and "af6775193721b9f6c72802349eadfed39809f354" have entirely different histories.

13 changed files with 245 additions and 381 deletions

123
README.md
View File

@ -139,16 +139,119 @@ sudo script/ethercat/stop_ethercat.sh eno1
sudo script/ethercat/stop_ethercat.sh eno1 --restore-network sudo script/ethercat/stop_ethercat.sh eno1 --restore-network
``` ```
## 组件文档 ## MotorService 与 Modbus TCP PLC
具体能力、配置、协议和安全边界由对应代码目录下的 README 维护: 工程包含从 gRPC `MotorService`、`MotorManager`、`AbstractMotor` 到
`ModbusTcpMotorBusRuntime` 的 CMVR PLC v1 电机控制链x86-64 的 libmodbus
3.1.11 已放在 `dependency/x86/third_party/modbus/3.1.11`
- [MotorService gRPC 接口](cmvr-es/service/README.md#motorservice) PLC 对接时特别注意:
- [电机设备模块](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)
Modbus Quick Stop 只是功能性停止,不能替代硬接线急停或驱动器 STO。AUBO - `host` 必须配置为 IPv4 字面量PLC boot ID 必须非零且每次重启变化;
JSON 接口只访问控制柜 Standard 数字 IO不访问安全 IO。 - owner 决策、命令 ACK 都必须回显对应 session重连不得执行旧 mailbox
- PLC 在进程启动时可以离线;连接 supervisor 会继续退避重试,离线期间状态
返回不可用且运动命令不会写 mailbox
- 状态区按 odd/even seqlock 发布,上位机使用
sequence-before → 64-word block → sequence-after 三段读取验证;
- 每次 `OpenCyclicPosition/Velocity` 创建新 stream epochPLC 必须原子清零
`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 接口开放。

View File

@ -18,6 +18,8 @@ cmvr_es.pb.txt
入口文件: 入口文件:
- [`cmvr_es.pb.txt`](cmvr_es.pb.txt) - [`cmvr_es.pb.txt`](cmvr_es.pb.txt)
- [`cmvr_es_ume.pb.txt`](cmvr_es_ume.pb.txt)UME 主端样例
- [`cmvr_es_robot.pb.txt`](cmvr_es_robot.pb.txt):人形机械臂从端样例
- [`manager/device_manager.pb.txt`](manager/device_manager.pb.txt) - [`manager/device_manager.pb.txt`](manager/device_manager.pb.txt)
- [`manager/task_manager.pb.txt`](manager/task_manager.pb.txt) - [`manager/task_manager.pb.txt`](manager/task_manager.pb.txt)
@ -109,48 +111,47 @@ output/bin/protoc \
该命令只验证 Proto Text 解析,不验证文件、设备、证书、网络和跨字段语义。最终仍需运行组件测试和进程烟雾测试。 该命令只验证 Proto Text 解析,不验证文件、设备、证书、网络和跨字段语义。最终仍需运行组件测试和进程烟雾测试。
## 双边遥操配置 ## 双边遥操部署样例
源码仓库保持唯一根入口 [`cmvr_es.pb.txt`](cmvr_es.pb.txt)。统一的 仓库提供两个相互独立的 CMVR-ES 配置入口:
[`manager/device_manager.pb.txt`](manager/device_manager.pb.txt) 已声明
`ume_left`、`ume_right`、`ti5_motors` 和 `right_arm`;统一的
[`manager/task_manager.pb.txt`](manager/task_manager.pb.txt) 已声明
`ume_teleop` 和 gRPC server。角色差异不通过增加新的源码根配置文件表达而由
两台机器各自的外部部署配置决定。
UME 主端部署配置应只启用本机需要的 UME 设备和 `ume_teleop` Task - UME 主端:[`cmvr_es_ume.pb.txt`](cmvr_es_ume.pb.txt),只声明
`ume_left``ume_right`。两条机械臂在 DeviceManager 层默认关闭,
- `ume_left`、`ume_right` 在 DeviceManager 层默认关闭; [`devices/arm/ume_arms.pb.txt`](devices/arm/ume_arms.pb.txt) 内部的
- [`devices/arm/ume_arms.pb.txt`](devices/arm/ume_arms.pb.txt) 内部的 `hardware_enabled` 也默认关闭;两层开关必须经过标定与安全验收后分别启用。
`hardware_enabled` 也默认关闭; 出站 `ume_teleop` Task 默认关闭,样例不包含机器人地址或凭据。当前 Task
- 两层硬件门必须在完成 CAN 映射、限位标定和安全验收后分别启用; 只实现会话/重连/心跳和 latest-only 指令邮箱,尚无生产算法调用
- 当前 Task 只实现会话、重连、心跳和 latest-only 指令邮箱,尚无生产算法调用
`submitSetpoint()`,返回 effort 也尚未接入本地触觉协调器。 `submitSetpoint()`,返回 effort 也尚未接入本地触觉协调器。
- 人形机械臂从端:[`cmvr_es_robot.pb.txt`](cmvr_es_robot.pb.txt),通用 gRPC
server 可以启动,但 `ti5_motors``right_arm` 仍默认关闭。生产
`ArmTeleop` 已实现真实 `RobotArm` 适配,但服务配置的 `enable` 显式关闭且
样例哈希故意留空。`MotorRobotArm.enable_teleop_group_servo` 目前只是预留字段;
因为现有 `servoJ` 仍是逐关节顺序写,代码即使看到该字段为 true 也会拒绝能力。
必须先实现并验收原子或定时的组下发原语。因此启动 gRPC server 不等于允许遥操
执行,也不能绕过设备层硬件门。
机器人从端部署配置应只启用经过验收的机械臂设备以及所需的 gRPC server 在两台边缘设备各自的源码或安装目录运行:
- `ti5_motors`、`right_arm` 默认关闭;
- `ArmTeleop` 服务后端默认关闭,模型哈希必须由部署配置明确给出;
- 当前 `MotorRobotArm::servoJ()` 仍是逐关节顺序写,不满足遥操作组伺服能力门;
- 启动 gRPC server 不代表允许遥操作执行,也不能绕过设备层硬件门。
部署时应把完整配置树分别复制到两台机器的外部目录,并继续使用相同的标准文件名:
```text
/etc/cmvr-es/ume/cmvr_es.pb.txt
/etc/cmvr-es/robot/cmvr_es.pb.txt
```
两套根配置都继续引用各自目录下同名的
`manager/device_manager.pb.txt``manager/task_manager.pb.txt`。运行命令为:
```bash ```bash
./output/bin/cmvr_es --config /etc/cmvr-es/ume/cmvr_es.pb.txt # UME 主端(源码配置)
./output/bin/cmvr_es --config /etc/cmvr-es/robot/cmvr_es.pb.txt ./output/bin/cmvr_es \
--config ./cmvr-es/config/cmvr_es_ume.pb.txt
# 人形机械臂从端(源码配置)
./output/bin/cmvr_es \
--config ./cmvr-es/config/cmvr_es_robot.pb.txt
``` ```
UME 主端需要填写从端地址、会话 manifest 和认证配置;机器人从端需要填写现场 如果使用安装后的配置副本,则相应命令为:
机械臂配置。不要把生产 IP、token、私钥或设备标定值提交到仓库默认配置。
```bash
./output/bin/cmvr_es --config ./output/bin/config/cmvr_es_ume.pb.txt
./output/bin/cmvr_es --config ./output/bin/config/cmvr_es_robot.pb.txt
```
上线前应把两套配置分别复制到两台机器的外部配置目录。主端需要填写从端地址、
会话 manifest 和认证配置;从端需要换成现场机械臂设备配置,并在真实硬件测试后
逐层开启。不要把生产 IP、token、私钥或设备标定值提交到仓库样例。
## 生产配置 ## 生产配置

View File

@ -0,0 +1,11 @@
# Follower robot-side CMVR-ES profile.
#
# The RobotArm ArmTeleop backend is implemented but explicitly disabled. The
# physical arm and service backend remain closed. Current MotorRobotArm
# sequential joint writes are rejected as a teleop group-servo capability until
# an atomic/timed group primitive and its safety timing gates are accepted.
cmvr_es {
logger_config_file: "logger/logger.pb.txt"
device_manager_config_file: "manager/device_manager_robot.pb.txt"
task_manager_config_file: "manager/task_manager_robot.pb.txt"
}

View File

@ -0,0 +1,10 @@
# UME leader-side CMVR-ES profile.
#
# All relative paths below are resolved from this file's directory. This
# checked-in profile contains no production endpoint, credentials or hardware
# enablement.
cmvr_es {
logger_config_file: "logger/logger.pb.txt"
device_manager_config_file: "manager/device_manager_ume.pb.txt"
task_manager_config_file: "manager/task_manager_ume.pb.txt"
}

View File

@ -0,0 +1,24 @@
# Follower robot-side devices.
device_manager {
name: "cmvr_es_robot"
version: "0.1"
description: "CMVR humanoid follower edge system"
init_all_motors_when_no_active_joints: false
devices {
id: "ti5_motors"
type: DEVICE_TYPE_MOTOR_SYSTEM
config_file: "devices/motor/ti5_motors.pb.txt"
# Physical motor communication remains fail-closed in this example.
enable: false
}
devices {
id: "right_arm"
type: DEVICE_TYPE_ROBOT_ARM
config_file: "devices/arm/arm.pb.txt"
# Do not enable until the motor system, URDF, limits, servoJ timing and
# independent emergency-stop path have passed the robot safety checkout.
enable: false
}
}

View File

@ -0,0 +1,24 @@
# UME leader-side devices only.
device_manager {
name: "cmvr_es_ume"
version: "0.1"
description: "UME leader edge system"
init_all_motors_when_no_active_joints: false
devices {
id: "ume_right"
type: DEVICE_TYPE_ROBOT_ARM
config_file: "devices/arm/ume_arms.pb.txt"
# Hardware gate 1/2. Gate 2/2 is ume.hardware_enabled in the arm config.
# Keep both false until CAN mapping, limits and physical safety are verified.
enable: false
}
devices {
id: "ume_left"
type: DEVICE_TYPE_ROBOT_ARM
config_file: "devices/arm/ume_arms.pb.txt"
# Hardware gate 1/2. Gate 2/2 is ume.hardware_enabled in the arm config.
enable: false
}
}

View File

@ -0,0 +1,14 @@
# Follower robot-side tasks.
task_manager {
tasks {
id: "grpc_server"
type: TASK_TYPE_GRPC_SERVER
run_mode: TASK_RUN_MODE_BLOCKING_SERVICE
config_file: "tasks/grpc_server_task/grpc_server_task.pb.txt"
# The generic gRPC server may be enabled for integration. This does not
# enable a physical arm: device entries and the implemented RobotArm
# ArmTeleop adapter are explicitly disabled. Current MotorRobotArm
# sequential joint dispatch also fails the group-servo capability gate.
enable: true
}
}

View File

@ -0,0 +1,12 @@
# UME leader-side tasks.
task_manager {
tasks {
id: "ume_teleop"
type: TASK_TYPE_UME_TELEOP
run_mode: TASK_RUN_MODE_BLOCKING_SERVICE
config_file: "tasks/ume_teleop_task/ume_teleop_task.pb.txt"
# Fail-closed: configure the follower endpoint, expected manifest and
# transport security before enabling outbound teleoperation.
enable: false
}
}

View File

@ -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 | | 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 | | 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](arm/aubo_arm/README.md)、Huayan、UME | | RobotArm | [`arm/robot_arm.h`](arm/robot_arm.h) | [`arm/robot_arm_factory.h`](arm/robot_arm_factory.h) | MotorRobotArm、AUBO、Huayan |
| DexHand | [`dexhand/abstract_dexhand.h`](dexhand/abstract_dexhand.h) | [`dexhand/dexhand_factory.h`](dexhand/dexhand_factory.h) | RH56DFTP、PX6AXGen3 | | 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 | | 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 | | 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 | | BioHead | [`biohead/abstract_biohead.h`](biohead/abstract_biohead.h) | DeviceFactory 直接创建 | BioHeadRobot |
| MotorSystem | [`motor/`](motor/README.md) | DeviceFactory 直接创建 | CAN、MuJoCo、EtherCAT、Modbus TCP PLC | | MotorSystem | `motor/motor_system/` | DeviceFactory 直接创建 | CAN/MuJoCo motor group |
代码目录存在不等于已经接入配置创建链: 代码目录存在不等于已经接入配置创建链:

View File

@ -1,124 +0,0 @@
# 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 拒绝验证。

View File

@ -1,82 +0,0 @@
# 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 和断网故障台架。

View File

@ -1,93 +0,0 @@
# 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()` 启动连接 supervisorPLC 可以在进程启动时离线;
- 离线期间状态不可用,运动命令必须在写 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、驱动器或硬件安全链已经验收。

View File

@ -21,42 +21,6 @@ gRPC 和 QUIC 的职责边界:
- 实时音视频使用 QUIC DATAGRAM - 实时音视频使用 QUIC DATAGRAM
- `quic_edge/` 不是平台 Gateway也不是浏览器服务器。 - `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)
- gRPCModbus 端到端测试:
[`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 ## 新增 gRPC Service
当前没有动态 service registry必须完成以下全部步骤。 当前没有动态 service registry必须完成以下全部步骤。