feat(aubo): auto recover after hardware estop release
This commit is contained in:
parent
cb5f46c598
commit
1b3cd55505
@ -17,6 +17,7 @@ arm {
|
||||
tool_frame: "tool0"
|
||||
username: "aubo"
|
||||
password: "123456"
|
||||
auto_power_on_after_hardware_estop_release: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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` 的可用性,也未说明释放
|
||||
急停开关后的控制器恢复时序。因此自动恢复必须在释放后再次清队列并完成上述
|
||||
安全确认;无法确认时不得解除锁存。“释放开关后零位移”的最终保证仍需真机
|
||||
|
||||
@ -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() !=
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 不做网络调用 |
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user