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"
|
tool_frame: "tool0"
|
||||||
username: "aubo"
|
username: "aubo"
|
||||||
password: "123456"
|
password: "123456"
|
||||||
|
auto_power_on_after_hardware_estop_release: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -108,16 +108,21 @@ cmake --install build
|
|||||||
所有 Move、Speed、Servo 和程序启动请求均按不安全状态拒绝;
|
所有 Move、Speed、Servo 和程序启动请求均按不安全状态拒绝;
|
||||||
- 硬件急停会立即使当前运动 generation 失效,并在急停输入有效期间保持锁存。
|
- 硬件急停会立即使当前运动 generation 失效,并在急停输入有效期间保持锁存。
|
||||||
检测到硬件急停输入消失且控制器重新报告 `Normal`/`ReducedMode` 后,后端应
|
检测到硬件急停输入消失且控制器重新报告 `Normal`/`ReducedMode` 后,后端应
|
||||||
自动执行安全恢复确认;防护停机和 Safety Fault/Violation 仍保持显式恢复语义;
|
自动执行 `poweron()` 和 `startup()`,恢复到 `Running` 后再完成安全确认并开放新的
|
||||||
|
gRPC 控制指令;防护停机和 Safety Fault/Violation 仍保持显式恢复语义;
|
||||||
- `emergencyStop()` 使用独立的 `SoftwareEmergencyStop` 锁存。即使软件急停在真实
|
- `emergencyStop()` 使用独立的 `SoftwareEmergencyStop` 锁存。即使软件急停在真实
|
||||||
硬件急停有效期间触发,后续硬件采样也不能覆盖该锁存,释放硬件急停开关不会
|
硬件急停有效期间触发,后续硬件采样也不能覆盖该锁存,释放硬件急停开关不会
|
||||||
自动清除软件急停;它只能通过显式安全恢复流程解除;
|
自动清除软件急停;它只能通过显式安全恢复流程解除;
|
||||||
- 锁存后会终止直接运动与程序、关闭 servo 模式并清理控制器轨迹。硬件急停
|
- 锁存后会终止直接运动与程序、关闭 servo 模式并清理控制器轨迹。硬件急停
|
||||||
自动恢复只有在确认 `ExecId == -1`、普通队列和轨迹队列均为空、运行时已停止
|
自动恢复先上电到 `Idle`,在刹车释放前清理 runtime、servo 和轨迹队列,再执行
|
||||||
且机械臂稳定后才能解除锁存;如果自动确认失败,则继续保持 fail-closed,
|
`startup()`;到达 `Running` 后还会再次确认 `ExecId == -1`、普通队列和轨迹队列
|
||||||
并允许通过 `torqueOn`/`clearFault`/`unlockProtectiveStop` 显式重试恢复;
|
均为空、运行时已停止且机械臂稳定,全部成立后才能解除锁存;
|
||||||
- 恢复流程不会调用 `resume`、`arbitraryResume`、`startMove`,也不会重新提交
|
- 当前 AUBO 配置通过 `auto_power_on_after_hardware_estop_release: true` 显式启用自动
|
||||||
急停前的目标、速度、servo 指令或程序;
|
上电。自动确认失败时继续保持 fail-closed,并允许通过 `torqueOn`/`clearFault`/
|
||||||
|
`unlockProtectiveStop` 显式重试;本轮释放期间收到 `stopMotion()` 或 `torqueOff()`
|
||||||
|
会取消自动上电,显式停止始终优先;
|
||||||
|
- 恢复流程只调用 `poweron()` 和 `startup()`,不会调用 `resume`、`arbitraryResume`、
|
||||||
|
`startMove`,也不会重新提交急停前的目标、速度、servo 指令或程序;
|
||||||
- AUBO SDK 未在本地文档中保证急停期间 `clearPath` 的可用性,也未说明释放
|
- AUBO SDK 未在本地文档中保证急停期间 `clearPath` 的可用性,也未说明释放
|
||||||
急停开关后的控制器恢复时序。因此自动恢复必须在释放后再次清队列并完成上述
|
急停开关后的控制器恢复时序。因此自动恢复必须在释放后再次清队列并完成上述
|
||||||
安全确认;无法确认时不得解除锁存。“释放开关后零位移”的最终保证仍需真机
|
安全确认;无法确认时不得解除锁存。“释放开关后零位移”的最终保证仍需真机
|
||||||
|
|||||||
@ -175,11 +175,13 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool completeHardwareEmergencyStop(
|
bool completeHardwareEmergencyStop(
|
||||||
|
const bool robot_running,
|
||||||
const bool controller_idle,
|
const bool controller_idle,
|
||||||
const bool cancellation_confirmed)
|
const bool cancellation_confirmed)
|
||||||
{
|
{
|
||||||
if (!state_->completeHardwareEmergencyStopRecovery(
|
if (!state_->completeHardwareEmergencyStopRecovery(
|
||||||
token_, controller_idle, cancellation_confirmed)) {
|
token_, robot_running, controller_idle,
|
||||||
|
cancellation_confirmed)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
completed_ = true;
|
completed_ = true;
|
||||||
@ -392,6 +394,7 @@ struct AuboSafetyMonitor final {
|
|||||||
static_cast<int>(RuntimeState::Stopped)};
|
static_cast<int>(RuntimeState::Stopped)};
|
||||||
std::atomic<int> emergency_stop_source{-1};
|
std::atomic<int> emergency_stop_source{-1};
|
||||||
std::atomic<bool> hardware_emergency_stop_latched{false};
|
std::atomic<bool> hardware_emergency_stop_latched{false};
|
||||||
|
std::atomic<bool> automatic_recovery_suppressed{false};
|
||||||
std::atomic<int> servo_mode_select{0};
|
std::atomic<int> servo_mode_select{0};
|
||||||
std::atomic<std::int64_t> last_sample_ns{0};
|
std::atomic<std::int64_t> last_sample_ns{0};
|
||||||
std::atomic<bool> cancellation_confirmed{true};
|
std::atomic<bool> cancellation_confirmed{true};
|
||||||
@ -403,6 +406,8 @@ struct AuboSafetyMonitor final {
|
|||||||
std::condition_variable wait_cv;
|
std::condition_variable wait_cv;
|
||||||
std::mutex termination_mutex;
|
std::mutex termination_mutex;
|
||||||
std::recursive_mutex command_rpc_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;
|
std::string arm_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -466,7 +471,11 @@ void publishSafetySample(
|
|||||||
monitor->last_sample_ns.store(monotonicNowNs());
|
monitor->last_sample_ns.store(monotonicNowNs());
|
||||||
|
|
||||||
if (emergency_stop_source != 0) {
|
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) {
|
} else if (!current.latched) {
|
||||||
monitor->hardware_emergency_stop_latched.store(false);
|
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<arcs::aubo_sdk::RpcClient>& rpc_client,
|
||||||
const std::shared_ptr<AuboSafetyMonitor>& monitor)
|
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);
|
std::unique_lock termination_lock(monitor->termination_mutex);
|
||||||
monitor->motion_state->cancelActiveForSafety();
|
monitor->motion_state->cancelActiveForSafety();
|
||||||
auto stop_request = monitor->motion_state->beginStop();
|
auto stop_request = monitor->motion_state->beginStop();
|
||||||
@ -854,6 +865,185 @@ bool controllerStillQuiescent(
|
|||||||
RuntimeState::Stopped;
|
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(
|
void runSafetyMonitor(
|
||||||
const std::shared_ptr<AuboSafetyMonitor>& monitor,
|
const std::shared_ptr<AuboSafetyMonitor>& monitor,
|
||||||
const std::string& ip,
|
const std::string& ip,
|
||||||
@ -896,44 +1086,15 @@ void runSafetyMonitor(
|
|||||||
monitor
|
monitor
|
||||||
->hardware_emergency_stop_latched
|
->hardware_emergency_stop_latched
|
||||||
.load(),
|
.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) {
|
if (hardware_estop_released) {
|
||||||
const auto token =
|
(void)autoPowerOnAfterHardwareEmergencyStop(
|
||||||
monitor->safety_state->beginRecovery(
|
rpc_client, monitor, robot_interface);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
safety = monitor->safety_state->snapshot();
|
safety = monitor->safety_state->snapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1996,6 +2157,8 @@ Result AuboArm::torqueOn(
|
|||||||
}
|
}
|
||||||
emergency_stopped_.store(false);
|
emergency_stopped_.store(false);
|
||||||
servo_mode_.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()) {
|
if (const auto cancelled = cancellation_result()) {
|
||||||
return *cancelled;
|
return *cancelled;
|
||||||
}
|
}
|
||||||
@ -2021,22 +2184,44 @@ Result AuboArm::torqueOn(
|
|||||||
|
|
||||||
Result AuboArm::torqueOff()
|
Result AuboArm::torqueOff()
|
||||||
{
|
{
|
||||||
const auto ready = ensureConnected_("torqueOff");
|
std::shared_ptr<arcs::aubo_sdk::RpcClient> rpc_client;
|
||||||
if (!ready.ok()) {
|
std::shared_ptr<AuboSafetyMonitor> monitor;
|
||||||
return ready;
|
{
|
||||||
|
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 {
|
try {
|
||||||
const auto robot_names = sdk_->rpc_client->getRobotNames();
|
std::unique_lock<std::recursive_mutex> command_rpc_lock;
|
||||||
if (robot_names.empty()) {
|
if (monitor) {
|
||||||
return Result::failure(ArmErrorCode::RobotNotReady, "[AuboArm] robot name list is empty");
|
command_rpc_lock = std::unique_lock<std::recursive_mutex>(
|
||||||
|
monitor->command_rpc_mutex);
|
||||||
}
|
}
|
||||||
auto robot_interface = sdk_->rpc_client->getRobotInterface(robot_names.front());
|
Result interface_result;
|
||||||
if (!robot_interface) {
|
auto robot_interface = getPrimaryRobotInterface(
|
||||||
return Result::failure(ArmErrorCode::RobotNotReady, "[AuboArm] robot interface is null");
|
rpc_client, "torqueOff", interface_result);
|
||||||
|
if (!interface_result.ok()) {
|
||||||
|
return interface_result;
|
||||||
}
|
}
|
||||||
robot_interface->getRobotManage()->poweroff();
|
const int power_off_ret =
|
||||||
if (!waitForRobotMode(robot_interface, arcs::common_interface::RobotModeType::PowerOff)) {
|
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::failure(ArmErrorCode::CommandFailed, "[AuboArm] torqueOff failed: timeout waiting for PowerOff");
|
||||||
}
|
}
|
||||||
return Result::success();
|
return Result::success();
|
||||||
@ -2582,6 +2767,13 @@ Result AuboArm::stopMotion_(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto motion_state = sdk_->motion_state;
|
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 forced_kind =
|
||||||
aubo_internal::MotionKind::None;
|
aubo_internal::MotionKind::None;
|
||||||
if (requested_kind == MotionStopKind::Joint) {
|
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->safety_monitor->motion_state =
|
||||||
sdk_state->motion_state;
|
sdk_state->motion_state;
|
||||||
sdk_state->safety_monitor->arm_id = id_;
|
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(
|
publishSafetySample(
|
||||||
sdk_state->safety_monitor,
|
sdk_state->safety_monitor,
|
||||||
robot_interface->getRobotState()->getSafetyModeType(),
|
robot_interface->getRobotState()->getSafetyModeType(),
|
||||||
@ -3709,12 +3909,21 @@ Result AuboArm::ensureMotionReady_(
|
|||||||
condition == aubo_internal::SafetyCondition::Violation) {
|
condition == aubo_internal::SafetyCondition::Violation) {
|
||||||
code = ArmErrorCode::RobotInFault;
|
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(
|
return Result::failure(
|
||||||
code,
|
code,
|
||||||
"[AuboArm] " + context +
|
"[AuboArm] " + context +
|
||||||
" rejected: hardware safety latch is " +
|
" rejected: hardware safety latch is " +
|
||||||
safetyConditionName(condition) +
|
safetyConditionName(condition) + recovery_instruction);
|
||||||
"; clear the hardware condition and perform explicit recovery");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (monitor->robot_mode.load() !=
|
if (monitor->robot_mode.load() !=
|
||||||
|
|||||||
@ -81,9 +81,12 @@ struct SafetySnapshot {
|
|||||||
inline bool shouldAutoRecoverHardwareEmergencyStop(
|
inline bool shouldAutoRecoverHardwareEmergencyStop(
|
||||||
const SafetySnapshot& snapshot,
|
const SafetySnapshot& snapshot,
|
||||||
const bool hardware_emergency_stop_was_observed,
|
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.recovery_in_progress &&
|
||||||
!snapshot.software_emergency_stop_latched &&
|
!snapshot.software_emergency_stop_latched &&
|
||||||
snapshot.latched_reason == SafetyCondition::RobotEmergencyStop &&
|
snapshot.latched_reason == SafetyCondition::RobotEmergencyStop &&
|
||||||
@ -175,10 +178,13 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hardware E-stop release may clear only this software latch. It does not
|
// The caller may clear the physical E-stop latch only after it has powered
|
||||||
// power on, release brakes, resume runtime, or issue a motion command.
|
// 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(
|
bool completeHardwareEmergencyStopRecovery(
|
||||||
const RecoveryToken token,
|
const RecoveryToken token,
|
||||||
|
const bool robot_running,
|
||||||
const bool controller_idle,
|
const bool controller_idle,
|
||||||
const bool cancellation_confirmed)
|
const bool cancellation_confirmed)
|
||||||
{
|
{
|
||||||
@ -187,7 +193,7 @@ public:
|
|||||||
!recovery_in_progress_ ||
|
!recovery_in_progress_ ||
|
||||||
software_emergency_stop_latched_ ||
|
software_emergency_stop_latched_ ||
|
||||||
latched_reason_ != SafetyCondition::RobotEmergencyStop ||
|
latched_reason_ != SafetyCondition::RobotEmergencyStop ||
|
||||||
!isMotionSafe(observed_) || !controller_idle ||
|
!isMotionSafe(observed_) || !robot_running || !controller_idle ||
|
||||||
!cancellation_confirmed) {
|
!cancellation_confirmed) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,9 +54,13 @@ int main()
|
|||||||
CHECK_TRUE(state.snapshot().latched);
|
CHECK_TRUE(state.snapshot().latched);
|
||||||
CHECK_TRUE(!state.tryPermit().has_value());
|
CHECK_TRUE(!state.tryPermit().has_value());
|
||||||
CHECK_TRUE(!shouldAutoRecoverHardwareEmergencyStop(
|
CHECK_TRUE(!shouldAutoRecoverHardwareEmergencyStop(
|
||||||
state.snapshot(), false, 0));
|
state.snapshot(), false, 0, true, false));
|
||||||
CHECK_TRUE(shouldAutoRecoverHardwareEmergencyStop(
|
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);
|
const auto recovery = state.beginRecovery(state.snapshot().epoch);
|
||||||
CHECK_TRUE(recovery.has_value());
|
CHECK_TRUE(recovery.has_value());
|
||||||
@ -65,8 +69,14 @@ int main()
|
|||||||
|
|
||||||
const auto retry = state.beginRecovery(state.snapshot().epoch);
|
const auto retry = state.beginRecovery(state.snapshot().epoch);
|
||||||
CHECK_TRUE(retry.has_value());
|
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(
|
CHECK_TRUE(state.completeHardwareEmergencyStopRecovery(
|
||||||
*retry, true, true));
|
*retry, true, true, true));
|
||||||
const auto recovered_permit = state.tryPermit();
|
const auto recovered_permit = state.tryPermit();
|
||||||
CHECK_TRUE(recovered_permit.has_value());
|
CHECK_TRUE(recovered_permit.has_value());
|
||||||
CHECK_TRUE(state.validate(*recovered_permit));
|
CHECK_TRUE(state.validate(*recovered_permit));
|
||||||
@ -80,7 +90,7 @@ int main()
|
|||||||
state.observe(SafetyCondition::RobotEmergencyStop);
|
state.observe(SafetyCondition::RobotEmergencyStop);
|
||||||
state.observe(SafetyCondition::Normal);
|
state.observe(SafetyCondition::Normal);
|
||||||
CHECK_TRUE(!shouldAutoRecoverHardwareEmergencyStop(
|
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().software_emergency_stop_latched);
|
||||||
CHECK_TRUE(state.snapshot().latched_reason ==
|
CHECK_TRUE(state.snapshot().latched_reason ==
|
||||||
SafetyCondition::SoftwareEmergencyStop);
|
SafetyCondition::SoftwareEmergencyStop);
|
||||||
@ -88,7 +98,7 @@ int main()
|
|||||||
state.snapshot().epoch);
|
state.snapshot().epoch);
|
||||||
CHECK_TRUE(software_recovery.has_value());
|
CHECK_TRUE(software_recovery.has_value());
|
||||||
CHECK_TRUE(!state.completeHardwareEmergencyStopRecovery(
|
CHECK_TRUE(!state.completeHardwareEmergencyStopRecovery(
|
||||||
*software_recovery, true, true));
|
*software_recovery, true, true, true));
|
||||||
state.failRecovery(*software_recovery);
|
state.failRecovery(*software_recovery);
|
||||||
const auto explicit_software_recovery = state.beginRecovery(
|
const auto explicit_software_recovery = state.beginRecovery(
|
||||||
state.snapshot().epoch);
|
state.snapshot().epoch);
|
||||||
@ -104,7 +114,7 @@ int main()
|
|||||||
state.snapshot().epoch);
|
state.snapshot().epoch);
|
||||||
CHECK_TRUE(stale_recovery.has_value());
|
CHECK_TRUE(stale_recovery.has_value());
|
||||||
CHECK_TRUE(!state.completeHardwareEmergencyStopRecovery(
|
CHECK_TRUE(!state.completeHardwareEmergencyStopRecovery(
|
||||||
*stale_recovery, true, true));
|
*stale_recovery, true, true, true));
|
||||||
state.failRecovery(*stale_recovery);
|
state.failRecovery(*stale_recovery);
|
||||||
const auto explicit_recovery = state.beginRecovery(
|
const auto explicit_recovery = state.beginRecovery(
|
||||||
state.snapshot().epoch);
|
state.snapshot().epoch);
|
||||||
|
|||||||
@ -29,9 +29,10 @@
|
|||||||
`RECOVERY_LOCAL_ONLY`、服务端确认实际 peer 为 loopback/Unix socket 且持久审计可写时才可开放。
|
`RECOVERY_LOCAL_ONLY`、服务端确认实际 peer 为 loopback/Unix socket 且持久审计可写时才可开放。
|
||||||
|
|
||||||
AUBO 另有一条设备内硬件语义:真实硬件急停曾有效、随后输入消失且控制器重新报告
|
AUBO 另有一条设备内硬件语义:真实硬件急停曾有效、随后输入消失且控制器重新报告
|
||||||
`Normal/ReducedMode` 时,驱动会在重新清理队列并确认 quiescent 后自动解除该硬件锁存;
|
`Normal/ReducedMode` 时,驱动会自动上电到 `Idle`、清理旧队列、执行 `startup()`,并在
|
||||||
|
`Running` 下再次确认 quiescent 后解除该硬件锁存,使新的 gRPC 指令可以重新准入;
|
||||||
软件 `emergencyStop()` 使用独立 `SoftwareEmergencyStop` 锁存,即使它与硬件急停重叠也绝不被
|
软件 `emergencyStop()` 使用独立 `SoftwareEmergencyStop` 锁存,即使它与硬件急停重叠也绝不被
|
||||||
硬件输入释放自动清除。自动流程不上电、不 resume、不重放旧目标。
|
硬件输入释放自动清除。自动流程不 resume、不重放旧目标;显式 Stop/PowerOff 会取消本轮自动上电。
|
||||||
|
|
||||||
## 1. 决策摘要
|
## 1. 决策摘要
|
||||||
|
|
||||||
@ -131,7 +132,8 @@ AUBO 另有一条设备内硬件语义:真实硬件急停曾有效、随后输
|
|||||||
共享服务端生成的 `anonymous` principal,因此 command ID 必须在整个匿名部署内唯一。
|
共享服务端生成的 `anonymous` principal,因此 command ID 必须在整个匿名部署内唯一。
|
||||||
6. 相同 ID、不同语义 payload 必须返回冲突,不能覆盖旧记录。
|
6. 相同 ID、不同语义 payload 必须返回冲突,不能覆盖旧记录。
|
||||||
7. 硬件结果不确定时保存 `OUTCOME_UNKNOWN`,重试只能查询该结果,不能再次下发。
|
7. 硬件结果不确定时保存 `OUTCOME_UNKNOWN`,重试只能查询该结果,不能再次下发。
|
||||||
8. 恢复成功只表示软件准入可重新评估,不表示设备被上电、使能、解除急停或自动运动。
|
8. 通用 `RecoverSafetyState` 成功只表示软件准入可重新评估,不表示设备被上电、使能、
|
||||||
|
解除急停或自动运动。AUBO 物理急停释放后的自动上电是独立、显式配置的设备内策略。
|
||||||
9. `SafetyManager` 持有内部锁时不得调用设备、网络或可能阻塞的 participant 方法。
|
9. `SafetyManager` 持有内部锁时不得调用设备、网络或可能阻塞的 participant 方法。
|
||||||
10. 驱动最终安全检查失败时,即使已经获得 permit,也不能下发设备命令。
|
10. 驱动最终安全检查失败时,即使已经获得 permit,也不能下发设备命令。
|
||||||
11. 进程重启后,控制设备在新鲜状态确认完成前不能自动恢复到可控制状态。
|
11. 进程重启后,控制设备在新鲜状态确认完成前不能自动恢复到可控制状态。
|
||||||
@ -1286,7 +1288,7 @@ capability manifest/SystemInfo。
|
|||||||
|
|
||||||
| 设备/入口 | 策略 | 关键安全事实 | Stop/恢复要点 |
|
| 设备/入口 | 策略 | 关键安全事实 | 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 |
|
| 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 |
|
| 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 不做网络调用 |
|
| 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 tool_frame = 8;
|
||||||
string username = 9;
|
string username = 9;
|
||||||
string password = 10;
|
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 {
|
enum DamiaoMotorModel {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user