Compare commits

...

2 Commits

Author SHA1 Message Date
edb01463ff docs: move hardware guides into component directories
Keep the root README concise while documenting MotorService, the Modbus TCP PLC runtime, the motor stack, and AUBO cabinet IO next to their owning code.
2026-07-31 09:39:44 +08:00
b54c2936be refactor: restore single root configuration
Remove redundant UME and robot root profiles, keep the canonical cmvr_es configuration tree, and document per-host external deployment configuration.
2026-07-31 09:39:21 +08:00
13 changed files with 377 additions and 241 deletions

123
README.md
View File

@ -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 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 接口开放。
Modbus Quick Stop 只是功能性停止,不能替代硬接线急停或驱动器 STO。AUBO
JSON 接口只访问控制柜 Standard 数字 IO不访问安全 IO。

View File

@ -18,8 +18,6 @@ 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/task_manager.pb.txt`](manager/task_manager.pb.txt)
@ -111,47 +109,48 @@ output/bin/protoc \
该命令只验证 Proto Text 解析,不验证文件、设备、证书、网络和跨字段语义。最终仍需运行组件测试和进程烟雾测试。
## 双边遥操部署样例
## 双边遥操配置
仓库提供两个相互独立的 CMVR-ES 配置入口:
源码仓库保持唯一根入口 [`cmvr_es.pb.txt`](cmvr_es.pb.txt)。统一的
[`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 主端:[`cmvr_es_ume.pb.txt`](cmvr_es_ume.pb.txt),只声明
`ume_left``ume_right`。两条机械臂在 DeviceManager 层默认关闭,
[`devices/arm/ume_arms.pb.txt`](devices/arm/ume_arms.pb.txt) 内部的
`hardware_enabled` 也默认关闭;两层开关必须经过标定与安全验收后分别启用。
出站 `ume_teleop` Task 默认关闭,样例不包含机器人地址或凭据。当前 Task
只实现会话/重连/心跳和 latest-only 指令邮箱,尚无生产算法调用
UME 主端部署配置应只启用本机需要的 UME 设备和 `ume_teleop` Task
- `ume_left`、`ume_right` 在 DeviceManager 层默认关闭;
- [`devices/arm/ume_arms.pb.txt`](devices/arm/ume_arms.pb.txt) 内部的
`hardware_enabled` 也默认关闭;
- 两层硬件门必须在完成 CAN 映射、限位标定和安全验收后分别启用;
- 当前 Task 只实现会话、重连、心跳和 latest-only 指令邮箱,尚无生产算法调用
`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
```bash
# UME 主端(源码配置)
./output/bin/cmvr_es \
--config ./cmvr-es/config/cmvr_es_ume.pb.txt
- `ti5_motors`、`right_arm` 默认关闭;
- `ArmTeleop` 服务后端默认关闭,模型哈希必须由部署配置明确给出;
- 当前 `MotorRobotArm::servoJ()` 仍是逐关节顺序写,不满足遥操作组伺服能力门;
- 启动 gRPC server 不代表允许遥操作执行,也不能绕过设备层硬件门。
# 人形机械臂从端(源码配置)
./output/bin/cmvr_es \
--config ./cmvr-es/config/cmvr_es_robot.pb.txt
部署时应把完整配置树分别复制到两台机器的外部目录,并继续使用相同的标准文件名:
```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
./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
./output/bin/cmvr_es --config /etc/cmvr-es/ume/cmvr_es.pb.txt
./output/bin/cmvr_es --config /etc/cmvr-es/robot/cmvr_es.pb.txt
```
上线前应把两套配置分别复制到两台机器的外部配置目录。主端需要填写从端地址、
会话 manifest 和认证配置;从端需要换成现场机械臂设备配置,并在真实硬件测试后
逐层开启。不要把生产 IP、token、私钥或设备标定值提交到仓库样例。
UME 主端需要填写从端地址、会话 manifest 和认证配置;机器人从端需要填写现场
机械臂配置。不要把生产 IP、token、私钥或设备标定值提交到仓库默认配置。
## 生产配置

View File

@ -1,11 +0,0 @@
# 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

@ -1,10 +0,0 @@
# 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

@ -1,24 +0,0 @@
# 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

@ -1,24 +0,0 @@
# 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

@ -1,14 +0,0 @@
# 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

@ -1,12 +0,0 @@
# 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 |
| 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 |
代码目录存在不等于已经接入配置创建链:

View File

@ -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 拒绝验证。

View File

@ -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 和断网故障台架。

View File

@ -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()` 启动连接 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,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)
- 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
当前没有动态 service registry必须完成以下全部步骤。