feat(aubo): auto recover after hardware estop release

This commit is contained in:
xtkuang 2026-08-17 12:16:20 +08:00
parent cb5f46c598
commit 1b3cd55505
7 changed files with 309 additions and 73 deletions

View File

@ -17,6 +17,7 @@ arm {
tool_frame: "tool0"
username: "aubo"
password: "123456"
auto_power_on_after_hardware_estop_release: true
}
}
}

View File

@ -108,16 +108,21 @@ cmake --install build
所有 Move、Speed、Servo 和程序启动请求均按不安全状态拒绝;
- 硬件急停会立即使当前运动 generation 失效,并在急停输入有效期间保持锁存。
检测到硬件急停输入消失且控制器重新报告 `Normal`/`ReducedMode` 后,后端应
自动执行安全恢复确认;防护停机和 Safety Fault/Violation 仍保持显式恢复语义;
自动执行 `poweron()` 和 `startup()`,恢复到 `Running` 后再完成安全确认并开放新的
gRPC 控制指令;防护停机和 Safety Fault/Violation 仍保持显式恢复语义;
- `emergencyStop()` 使用独立的 `SoftwareEmergencyStop` 锁存。即使软件急停在真实
硬件急停有效期间触发,后续硬件采样也不能覆盖该锁存,释放硬件急停开关不会
自动清除软件急停;它只能通过显式安全恢复流程解除;
- 锁存后会终止直接运动与程序、关闭 servo 模式并清理控制器轨迹。硬件急停
自动恢复只有在确认 `ExecId == -1`、普通队列和轨迹队列均为空、运行时已停止
且机械臂稳定后才能解除锁存;如果自动确认失败,则继续保持 fail-closed,
并允许通过 `torqueOn`/`clearFault`/`unlockProtectiveStop` 显式重试恢复;
- 恢复流程不会调用 `resume`、`arbitraryResume`、`startMove`,也不会重新提交
急停前的目标、速度、servo 指令或程序;
自动恢复先上电到 `Idle`,在刹车释放前清理 runtime、servo 和轨迹队列,再执行
`startup()`;到达 `Running` 后还会再次确认 `ExecId == -1`、普通队列和轨迹队列
均为空、运行时已停止且机械臂稳定,全部成立后才能解除锁存;
- 当前 AUBO 配置通过 `auto_power_on_after_hardware_estop_release: true` 显式启用自动
上电。自动确认失败时继续保持 fail-closed,并允许通过 `torqueOn`/`clearFault`/
`unlockProtectiveStop` 显式重试;本轮释放期间收到 `stopMotion()` 或 `torqueOff()`
会取消自动上电,显式停止始终优先;
- 恢复流程只调用 `poweron()` 和 `startup()`,不会调用 `resume`、`arbitraryResume`、
`startMove`,也不会重新提交急停前的目标、速度、servo 指令或程序;
- AUBO SDK 未在本地文档中保证急停期间 `clearPath` 的可用性,也未说明释放
急停开关后的控制器恢复时序。因此自动恢复必须在释放后再次清队列并完成上述
安全确认;无法确认时不得解除锁存。“释放开关后零位移”的最终保证仍需真机

View File

@ -175,11 +175,13 @@ public:
}
bool completeHardwareEmergencyStop(
const bool robot_running,
const bool controller_idle,
const bool cancellation_confirmed)
{
if (!state_->completeHardwareEmergencyStopRecovery(
token_, controller_idle, cancellation_confirmed)) {
token_, robot_running, controller_idle,
cancellation_confirmed)) {
return false;
}
completed_ = true;
@ -392,6 +394,7 @@ struct AuboSafetyMonitor final {
static_cast<int>(RuntimeState::Stopped)};
std::atomic<int> emergency_stop_source{-1};
std::atomic<bool> hardware_emergency_stop_latched{false};
std::atomic<bool> automatic_recovery_suppressed{false};
std::atomic<int> servo_mode_select{0};
std::atomic<std::int64_t> last_sample_ns{0};
std::atomic<bool> cancellation_confirmed{true};
@ -403,6 +406,8 @@ struct AuboSafetyMonitor final {
std::condition_variable wait_cv;
std::mutex termination_mutex;
std::recursive_mutex command_rpc_mutex;
bool auto_power_on_after_hardware_estop_release{false};
std::function<void()> on_hardware_estop_auto_recovered;
std::string arm_id;
};
@ -466,7 +471,11 @@ void publishSafetySample(
monitor->last_sample_ns.store(monotonicNowNs());
if (emergency_stop_source != 0) {
monitor->hardware_emergency_stop_latched.store(true);
const bool first_sample_for_event =
!monitor->hardware_emergency_stop_latched.exchange(true);
if (first_sample_for_event) {
monitor->automatic_recovery_suppressed.store(false);
}
} else if (!current.latched) {
monitor->hardware_emergency_stop_latched.store(false);
}
@ -568,6 +577,8 @@ bool enforceControllerTermination(
const std::shared_ptr<arcs::aubo_sdk::RpcClient>& rpc_client,
const std::shared_ptr<AuboSafetyMonitor>& monitor)
{
std::unique_lock<std::recursive_mutex> command_rpc_lock(
monitor->command_rpc_mutex);
std::unique_lock termination_lock(monitor->termination_mutex);
monitor->motion_state->cancelActiveForSafety();
auto stop_request = monitor->motion_state->beginStop();
@ -854,6 +865,185 @@ bool controllerStillQuiescent(
RuntimeState::Stopped;
}
bool hardwareEmergencyStopRecoveryCurrent(
const std::shared_ptr<AuboSafetyMonitor>& monitor,
const aubo_internal::RecoveryToken token)
{
const auto snapshot = monitor->safety_state->snapshot();
return token.valid() && !monitor->stop_requested.load() &&
!monitor->automatic_recovery_suppressed.load() &&
monitor->emergency_stop_source.load() == 0 && snapshot.latched &&
snapshot.recovery_in_progress && snapshot.epoch == token.epoch &&
!snapshot.software_emergency_stop_latched &&
snapshot.latched_reason ==
aubo_internal::SafetyCondition::RobotEmergencyStop &&
aubo_internal::isMotionSafe(snapshot.observed);
}
bool waitForHardwareEmergencyStopRecoveryMode(
const RobotInterfacePtr& robot_interface,
const std::shared_ptr<AuboSafetyMonitor>& monitor,
const aubo_internal::RecoveryToken token,
const RobotModeType target_mode)
{
const auto deadline =
std::chrono::steady_clock::now() + std::chrono::seconds(20);
while (std::chrono::steady_clock::now() < deadline) {
if (!hardwareEmergencyStopRecoveryCurrent(monitor, token)) {
return false;
}
if (robot_interface->getRobotState()->getRobotModeType() ==
target_mode) {
return true;
}
if (monitorWait(monitor, std::chrono::milliseconds(100))) {
return false;
}
}
return false;
}
bool autoPowerOnAfterHardwareEmergencyStop(
const std::shared_ptr<arcs::aubo_sdk::RpcClient>& rpc_client,
const std::shared_ptr<AuboSafetyMonitor>& monitor,
const RobotInterfacePtr& robot_interface)
{
std::unique_lock<std::recursive_mutex> command_rpc_lock(
monitor->command_rpc_mutex);
refreshSafetySample(rpc_client, monitor, robot_interface);
const auto snapshot = monitor->safety_state->snapshot();
if (!aubo_internal::shouldAutoRecoverHardwareEmergencyStop(
snapshot,
monitor->hardware_emergency_stop_latched.load(),
monitor->emergency_stop_source.load(),
monitor->auto_power_on_after_hardware_estop_release,
monitor->automatic_recovery_suppressed.load())) {
return false;
}
const auto token = monitor->safety_state->beginRecovery(snapshot.epoch);
if (!token.has_value()) {
return false;
}
SafetyRecoveryGuard recovery{monitor->safety_state, *token};
const auto fail = [&monitor](const std::string& detail) {
CMVR_LOG(WARNING)
<< "[AuboArm] hardware emergency-stop automatic power-on "
"failed, id="
<< monitor->arm_id << ", detail=" << detail;
return false;
};
try {
cancelForSafetyTransition(monitor);
if (!hardwareEmergencyStopRecoveryCurrent(monitor, *token)) {
return fail("recovery was cancelled before controller setup");
}
double mass = 0.0;
std::vector<double> cog(3, 0.0);
std::vector<double> aom(3, 0.0);
std::vector<double> inertia(6, 0.0);
const int payload_ret = robot_interface->getRobotConfig()->setPayload(
mass, cog, aom, inertia);
if (payload_ret != arcs::common_interface::AUBO_OK) {
return fail("setPayload ret=" + std::to_string(payload_ret));
}
if (!hardwareEmergencyStopRecoveryCurrent(monitor, *token)) {
return fail("recovery was cancelled after payload setup");
}
auto current_mode =
robot_interface->getRobotState()->getRobotModeType();
if (current_mode != RobotModeType::Running &&
current_mode != RobotModeType::Idle) {
const int power_on_ret =
robot_interface->getRobotManage()->poweron();
if (power_on_ret != arcs::common_interface::AUBO_OK) {
return fail("poweron ret=" + std::to_string(power_on_ret));
}
if (!waitForHardwareEmergencyStopRecoveryMode(
robot_interface, monitor, *token,
RobotModeType::Idle)) {
return fail("Idle was not reached after poweron");
}
current_mode = RobotModeType::Idle;
}
refreshSafetySample(rpc_client, monitor, robot_interface);
if (!hardwareEmergencyStopRecoveryCurrent(monitor, *token)) {
return fail("safety state changed before brake release");
}
const bool cleanup_ok = current_mode == RobotModeType::Running
? enforceControllerTermination(rpc_client, monitor)
: prepareControllerForStartup(rpc_client, monitor);
if (!cleanup_ok) {
return fail("old runtime, servo, or path state could not be cleared");
}
if (!hardwareEmergencyStopRecoveryCurrent(monitor, *token)) {
return fail("safety state changed during pre-startup cleanup");
}
if (current_mode != RobotModeType::Running) {
const int startup_ret =
robot_interface->getRobotManage()->startup();
if (startup_ret != arcs::common_interface::AUBO_OK) {
return fail("startup ret=" + std::to_string(startup_ret));
}
if (!waitForHardwareEmergencyStopRecoveryMode(
robot_interface, monitor, *token,
RobotModeType::Running)) {
return fail("Running was not reached after startup");
}
}
refreshSafetySample(rpc_client, monitor, robot_interface);
if (!hardwareEmergencyStopRecoveryCurrent(monitor, *token) ||
monitor->robot_mode.load() !=
static_cast<int>(RobotModeType::Running)) {
return fail("controller safety changed during startup");
}
// Startup is allowed to energize the arm, but it must not revive an
// old controller operation. Terminate once more in Running mode and
// require a fresh empty/steady observation before reopening commands.
cancelForSafetyTransition(monitor);
if (!enforceControllerTermination(rpc_client, monitor)) {
return fail("post-startup controller quiescence was not confirmed");
}
refreshSafetySample(rpc_client, monitor, robot_interface);
const bool robot_running =
monitor->robot_mode.load() ==
static_cast<int>(RobotModeType::Running);
const bool controller_idle =
robot_running &&
hardwareEmergencyStopRecoveryCurrent(monitor, *token) &&
controllerStillQuiescent(rpc_client, robot_interface);
if (!recovery.completeHardwareEmergencyStop(
robot_running,
controller_idle,
monitor->cancellation_confirmed.load())) {
return fail("safety epoch changed before recovery commit");
}
monitor->hardware_emergency_stop_latched.store(false);
monitor->automatic_recovery_suppressed.store(false);
if (monitor->on_hardware_estop_auto_recovered) {
monitor->on_hardware_estop_auto_recovered();
}
CMVR_LOG(INFO)
<< "[AuboArm] hardware emergency-stop release automatically "
"powered on and enabled, id="
<< monitor->arm_id;
return true;
} catch (const std::exception& error) {
return fail(error.what());
} catch (...) {
return fail("unknown exception");
}
}
void runSafetyMonitor(
const std::shared_ptr<AuboSafetyMonitor>& monitor,
const std::string& ip,
@ -896,44 +1086,15 @@ void runSafetyMonitor(
monitor
->hardware_emergency_stop_latched
.load(),
monitor->emergency_stop_source.load());
monitor->emergency_stop_source.load(),
monitor
->auto_power_on_after_hardware_estop_release,
monitor
->automatic_recovery_suppressed
.load());
if (hardware_estop_released) {
const auto token =
monitor->safety_state->beginRecovery(
safety.epoch);
if (token.has_value()) {
SafetyRecoveryGuard recovery{
monitor->safety_state, *token};
cancelForSafetyTransition(monitor);
const bool terminated =
enforceControllerTermination(
rpc_client, monitor);
refreshSafetySample(
(void)autoPowerOnAfterHardwareEmergencyStop(
rpc_client, monitor, robot_interface);
const bool controller_idle =
terminated &&
monitor->emergency_stop_source.load() ==
0 &&
aubo_internal::isMotionSafe(
monitor->safety_state->snapshot()
.observed) &&
controllerStillQuiescent(
rpc_client, robot_interface);
if (recovery.completeHardwareEmergencyStop(
controller_idle,
monitor->cancellation_confirmed
.load())) {
monitor->hardware_emergency_stop_latched
.store(false);
CMVR_LOG(INFO)
<< "[AuboArm] hardware emergency-stop release safely reconciled, id="
<< monitor->arm_id;
} else {
CMVR_LOG(WARNING)
<< "[AuboArm] hardware emergency-stop release remains latched because quiescence could not be confirmed, id="
<< monitor->arm_id;
}
}
safety = monitor->safety_state->snapshot();
}
@ -1996,6 +2157,8 @@ Result AuboArm::torqueOn(
}
emergency_stopped_.store(false);
servo_mode_.store(false);
monitor->hardware_emergency_stop_latched.store(false);
monitor->automatic_recovery_suppressed.store(false);
if (const auto cancelled = cancellation_result()) {
return *cancelled;
}
@ -2021,22 +2184,44 @@ Result AuboArm::torqueOn(
Result AuboArm::torqueOff()
{
std::shared_ptr<arcs::aubo_sdk::RpcClient> rpc_client;
std::shared_ptr<AuboSafetyMonitor> monitor;
{
std::lock_guard lock(mutex_);
const auto ready = ensureConnected_("torqueOff");
if (!ready.ok()) {
return ready;
}
rpc_client = sdk_->rpc_client;
monitor = sdk_->safety_monitor;
if (monitor) {
monitor->automatic_recovery_suppressed.store(true);
}
}
try {
const auto robot_names = sdk_->rpc_client->getRobotNames();
if (robot_names.empty()) {
return Result::failure(ArmErrorCode::RobotNotReady, "[AuboArm] robot name list is empty");
std::unique_lock<std::recursive_mutex> command_rpc_lock;
if (monitor) {
command_rpc_lock = std::unique_lock<std::recursive_mutex>(
monitor->command_rpc_mutex);
}
auto robot_interface = sdk_->rpc_client->getRobotInterface(robot_names.front());
if (!robot_interface) {
return Result::failure(ArmErrorCode::RobotNotReady, "[AuboArm] robot interface is null");
Result interface_result;
auto robot_interface = getPrimaryRobotInterface(
rpc_client, "torqueOff", interface_result);
if (!interface_result.ok()) {
return interface_result;
}
const int power_off_ret =
robot_interface->getRobotManage()->poweroff();
if (!waitForRobotMode(robot_interface, arcs::common_interface::RobotModeType::PowerOff)) {
if (power_off_ret != arcs::common_interface::AUBO_OK) {
return Result::failure(
ArmErrorCode::CommandFailed,
"[AuboArm] torqueOff failed: poweroff ret=" +
std::to_string(power_off_ret));
}
if (!waitForRobotMode(
robot_interface,
arcs::common_interface::RobotModeType::PowerOff)) {
return Result::failure(ArmErrorCode::CommandFailed, "[AuboArm] torqueOff failed: timeout waiting for PowerOff");
}
return Result::success();
@ -2582,6 +2767,13 @@ Result AuboArm::stopMotion_(
}
const auto motion_state = sdk_->motion_state;
const auto monitor = sdk_->safety_monitor;
std::unique_lock<std::recursive_mutex> command_rpc_lock;
if (monitor) {
monitor->automatic_recovery_suppressed.store(true);
command_rpc_lock = std::unique_lock<std::recursive_mutex>(
monitor->command_rpc_mutex);
}
aubo_internal::MotionKind forced_kind =
aubo_internal::MotionKind::None;
if (requested_kind == MotionStopKind::Joint) {
@ -3025,6 +3217,14 @@ Result AuboArm::connect(const std::string& ip, const int port)
sdk_state->safety_monitor->motion_state =
sdk_state->motion_state;
sdk_state->safety_monitor->arm_id = id_;
sdk_state->safety_monitor
->auto_power_on_after_hardware_estop_release =
vendor_cfg_.auto_power_on_after_hardware_estop_release();
sdk_state->safety_monitor->on_hardware_estop_auto_recovered =
[this] {
busy_.store(false);
servo_mode_.store(false);
};
publishSafetySample(
sdk_state->safety_monitor,
robot_interface->getRobotState()->getSafetyModeType(),
@ -3709,12 +3909,21 @@ Result AuboArm::ensureMotionReady_(
condition == aubo_internal::SafetyCondition::Violation) {
code = ArmErrorCode::RobotInFault;
}
std::string recovery_instruction =
"; clear the hardware condition and perform explicit recovery";
if (condition ==
aubo_internal::SafetyCondition::RobotEmergencyStop &&
monitor->auto_power_on_after_hardware_estop_release &&
!monitor->automatic_recovery_suppressed.load()) {
recovery_instruction =
"; release the physical emergency stop and wait for "
"automatic power-on recovery";
}
return Result::failure(
code,
"[AuboArm] " + context +
" rejected: hardware safety latch is " +
safetyConditionName(condition) +
"; clear the hardware condition and perform explicit recovery");
safetyConditionName(condition) + recovery_instruction);
}
if (monitor->robot_mode.load() !=

View File

@ -81,9 +81,12 @@ struct SafetySnapshot {
inline bool shouldAutoRecoverHardwareEmergencyStop(
const SafetySnapshot& snapshot,
const bool hardware_emergency_stop_was_observed,
const int current_emergency_stop_source) noexcept
const int current_emergency_stop_source,
const bool auto_power_on_enabled,
const bool automatic_recovery_suppressed) noexcept
{
return hardware_emergency_stop_was_observed && snapshot.latched &&
return auto_power_on_enabled && !automatic_recovery_suppressed &&
hardware_emergency_stop_was_observed && snapshot.latched &&
!snapshot.recovery_in_progress &&
!snapshot.software_emergency_stop_latched &&
snapshot.latched_reason == SafetyCondition::RobotEmergencyStop &&
@ -175,10 +178,13 @@ public:
return true;
}
// Hardware E-stop release may clear only this software latch. It does not
// power on, release brakes, resume runtime, or issue a motion command.
// The caller may clear the physical E-stop latch only after it has powered
// the controller, released the brakes, and then re-confirmed an empty,
// steady controller in Running mode. This never authorizes replaying the
// old target, runtime program, or servo session.
bool completeHardwareEmergencyStopRecovery(
const RecoveryToken token,
const bool robot_running,
const bool controller_idle,
const bool cancellation_confirmed)
{
@ -187,7 +193,7 @@ public:
!recovery_in_progress_ ||
software_emergency_stop_latched_ ||
latched_reason_ != SafetyCondition::RobotEmergencyStop ||
!isMotionSafe(observed_) || !controller_idle ||
!isMotionSafe(observed_) || !robot_running || !controller_idle ||
!cancellation_confirmed) {
return false;
}

View File

@ -54,9 +54,13 @@ int main()
CHECK_TRUE(state.snapshot().latched);
CHECK_TRUE(!state.tryPermit().has_value());
CHECK_TRUE(!shouldAutoRecoverHardwareEmergencyStop(
state.snapshot(), false, 0));
state.snapshot(), false, 0, true, false));
CHECK_TRUE(shouldAutoRecoverHardwareEmergencyStop(
state.snapshot(), true, 0));
state.snapshot(), true, 0, true, false));
CHECK_TRUE(!shouldAutoRecoverHardwareEmergencyStop(
state.snapshot(), true, 0, false, false));
CHECK_TRUE(!shouldAutoRecoverHardwareEmergencyStop(
state.snapshot(), true, 0, true, true));
const auto recovery = state.beginRecovery(state.snapshot().epoch);
CHECK_TRUE(recovery.has_value());
@ -65,8 +69,14 @@ int main()
const auto retry = state.beginRecovery(state.snapshot().epoch);
CHECK_TRUE(retry.has_value());
// Automatic release is not committed at Idle/PowerOn. The controller
// must have completed startup and reached Running first.
CHECK_TRUE(!state.completeHardwareEmergencyStopRecovery(
*retry, false, true, true));
CHECK_TRUE(!state.completeHardwareEmergencyStopRecovery(
*retry, true, false, true));
CHECK_TRUE(state.completeHardwareEmergencyStopRecovery(
*retry, true, true));
*retry, true, true, true));
const auto recovered_permit = state.tryPermit();
CHECK_TRUE(recovered_permit.has_value());
CHECK_TRUE(state.validate(*recovered_permit));
@ -80,7 +90,7 @@ int main()
state.observe(SafetyCondition::RobotEmergencyStop);
state.observe(SafetyCondition::Normal);
CHECK_TRUE(!shouldAutoRecoverHardwareEmergencyStop(
state.snapshot(), true, 0));
state.snapshot(), true, 0, true, false));
CHECK_TRUE(state.snapshot().software_emergency_stop_latched);
CHECK_TRUE(state.snapshot().latched_reason ==
SafetyCondition::SoftwareEmergencyStop);
@ -88,7 +98,7 @@ int main()
state.snapshot().epoch);
CHECK_TRUE(software_recovery.has_value());
CHECK_TRUE(!state.completeHardwareEmergencyStopRecovery(
*software_recovery, true, true));
*software_recovery, true, true, true));
state.failRecovery(*software_recovery);
const auto explicit_software_recovery = state.beginRecovery(
state.snapshot().epoch);
@ -104,7 +114,7 @@ int main()
state.snapshot().epoch);
CHECK_TRUE(stale_recovery.has_value());
CHECK_TRUE(!state.completeHardwareEmergencyStopRecovery(
*stale_recovery, true, true));
*stale_recovery, true, true, true));
state.failRecovery(*stale_recovery);
const auto explicit_recovery = state.beginRecovery(
state.snapshot().epoch);

View File

@ -29,9 +29,10 @@
`RECOVERY_LOCAL_ONLY`、服务端确认实际 peer 为 loopback/Unix socket 且持久审计可写时才可开放。
AUBO 另有一条设备内硬件语义:真实硬件急停曾有效、随后输入消失且控制器重新报告
`Normal/ReducedMode` 时,驱动会在重新清理队列并确认 quiescent 后自动解除该硬件锁存;
`Normal/ReducedMode` 时,驱动会自动上电到 `Idle`、清理旧队列、执行 `startup()`,并在
`Running` 下再次确认 quiescent 后解除该硬件锁存,使新的 gRPC 指令可以重新准入;
软件 `emergencyStop()` 使用独立 `SoftwareEmergencyStop` 锁存,即使它与硬件急停重叠也绝不被
硬件输入释放自动清除。自动流程不上电、不 resume、不重放旧目标。
硬件输入释放自动清除。自动流程不 resume、不重放旧目标;显式 Stop/PowerOff 会取消本轮自动上电。
## 1. 决策摘要
@ -131,7 +132,8 @@ AUBO 另有一条设备内硬件语义:真实硬件急停曾有效、随后输
共享服务端生成的 `anonymous` principal,因此 command ID 必须在整个匿名部署内唯一。
6. 相同 ID、不同语义 payload 必须返回冲突,不能覆盖旧记录。
7. 硬件结果不确定时保存 `OUTCOME_UNKNOWN`,重试只能查询该结果,不能再次下发。
8. 恢复成功只表示软件准入可重新评估,不表示设备被上电、使能、解除急停或自动运动。
8. 通用 `RecoverSafetyState` 成功只表示软件准入可重新评估,不表示设备被上电、使能、
解除急停或自动运动。AUBO 物理急停释放后的自动上电是独立、显式配置的设备内策略。
9. `SafetyManager` 持有内部锁时不得调用设备、网络或可能阻塞的 participant 方法。
10. 驱动最终安全检查失败时,即使已经获得 permit,也不能下发设备命令。
11. 进程重启后,控制设备在新鲜状态确认完成前不能自动恢复到可控制状态。
@ -1286,7 +1288,7 @@ capability manifest/SystemInfo。
| 设备/入口 | 策略 | 关键安全事实 | Stop/恢复要点 |
| --- | --- | --- | --- |
| Aubo Arm | Control | connected、robot mode、exec/queue、power、硬件/软件 EStop、protective stop、fault | 硬件 EStop 释放后仅在 Normal/Reduced、队列清空和 quiescent 确认后自动恢复;软件 EStop 独立锁存;无法确认 exec 时 OutcomeUnknown |
| Aubo Arm | Control | connected、robot mode、exec/queue、power、硬件/软件 EStop、protective stop、fault | 硬件 EStop 释放后在 Normal/Reduced 下自动 poweron/startup,Running 且队列清空、quiescent 后才重新准入;显式 Stop/PowerOff 优先;软件 EStop 独立锁存;无法确认 exec 时 OutcomeUnknown |
| Huayan Arm | Control | lifecycle generation、motion state、fault、stop confirmation | 保留已强化的 fail-closed 生命周期,映射为统一 endpoint |
| MotorRobotArm | Control | group atomicity、joint freshness、bus generation | 不具备原子 group servo 时继续拒绝 teleop capability |
| UME RobotArm | Control | CAN session、watchdog、torque enable、feedback freshness | reconnect 不恢复 torque;本地 haptic loop 不做网络调用 |

View File

@ -48,6 +48,9 @@ message VendorRobotArmBackendConfig {
string tool_frame = 8;
string username = 9;
string password = 10;
// AUBO only. Automatic energization after a physical E-stop is deliberately
// opt-in because releasing brakes changes the hardware energy state.
optional bool auto_power_on_after_hardware_estop_release = 11;
}
enum DamiaoMotorModel {