diff --git a/cmvr-es/config/devices/arm/aubo_arm.pb.txt b/cmvr-es/config/devices/arm/aubo_arm.pb.txt index 551f31df..cd8f3803 100644 --- a/cmvr-es/config/devices/arm/aubo_arm.pb.txt +++ b/cmvr-es/config/devices/arm/aubo_arm.pb.txt @@ -17,6 +17,7 @@ arm { tool_frame: "tool0" username: "aubo" password: "123456" + auto_power_on_after_hardware_estop_release: true } } } diff --git a/cmvr-es/devices/arm/aubo_arm/README.md b/cmvr-es/devices/arm/aubo_arm/README.md index 669aa42d..f895c6cd 100644 --- a/cmvr-es/devices/arm/aubo_arm/README.md +++ b/cmvr-es/devices/arm/aubo_arm/README.md @@ -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` 的可用性,也未说明释放 急停开关后的控制器恢复时序。因此自动恢复必须在释放后再次清队列并完成上述 安全确认;无法确认时不得解除锁存。“释放开关后零位移”的最终保证仍需真机 diff --git a/cmvr-es/devices/arm/aubo_arm/aubo_arm.cpp b/cmvr-es/devices/arm/aubo_arm/aubo_arm.cpp index d92619e6..4c0da356 100644 --- a/cmvr-es/devices/arm/aubo_arm/aubo_arm.cpp +++ b/cmvr-es/devices/arm/aubo_arm/aubo_arm.cpp @@ -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(RuntimeState::Stopped)}; std::atomic emergency_stop_source{-1}; std::atomic hardware_emergency_stop_latched{false}; + std::atomic automatic_recovery_suppressed{false}; std::atomic servo_mode_select{0}; std::atomic last_sample_ns{0}; std::atomic 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 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& rpc_client, const std::shared_ptr& monitor) { + std::unique_lock 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& 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& 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& rpc_client, + const std::shared_ptr& monitor, + const RobotInterfacePtr& robot_interface) +{ + std::unique_lock 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 cog(3, 0.0); + std::vector aom(3, 0.0); + std::vector 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(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(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& 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( - 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; - } - } + (void)autoPowerOnAfterHardwareEmergencyStop( + rpc_client, monitor, robot_interface); 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() { - const auto ready = ensureConnected_("torqueOff"); - if (!ready.ok()) { - return ready; + std::shared_ptr rpc_client; + std::shared_ptr 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 command_rpc_lock; + if (monitor) { + command_rpc_lock = std::unique_lock( + 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; } - robot_interface->getRobotManage()->poweroff(); - if (!waitForRobotMode(robot_interface, arcs::common_interface::RobotModeType::PowerOff)) { + const int power_off_ret = + robot_interface->getRobotManage()->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 command_rpc_lock; + if (monitor) { + monitor->automatic_recovery_suppressed.store(true); + command_rpc_lock = std::unique_lock( + 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() != diff --git a/cmvr-es/devices/arm/aubo_arm/aubo_safety_state.h b/cmvr-es/devices/arm/aubo_arm/aubo_safety_state.h index 54b5e750..b11fb94d 100644 --- a/cmvr-es/devices/arm/aubo_arm/aubo_safety_state.h +++ b/cmvr-es/devices/arm/aubo_arm/aubo_safety_state.h @@ -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; } diff --git a/cmvr-es/devices/arm/aubo_arm/tests/aubo_safety_state_test.cpp b/cmvr-es/devices/arm/aubo_arm/tests/aubo_safety_state_test.cpp index 8ae17007..716d2b00 100644 --- a/cmvr-es/devices/arm/aubo_arm/tests/aubo_safety_state_test.cpp +++ b/cmvr-es/devices/arm/aubo_arm/tests/aubo_safety_state_test.cpp @@ -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); diff --git a/docs/device_safety_control_plane_architecture.md b/docs/device_safety_control_plane_architecture.md index bf49c37e..d6675cd9 100644 --- a/docs/device_safety_control_plane_architecture.md +++ b/docs/device_safety_control_plane_architecture.md @@ -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 不做网络调用 | diff --git a/protos/cmvr/config/arm_config/arm_config.proto b/protos/cmvr/config/arm_config/arm_config.proto index a97be752..b717e89d 100644 --- a/protos/cmvr/config/arm_config/arm_config.proto +++ b/protos/cmvr/config/arm_config/arm_config.proto @@ -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 {