feat: add touch screen app class
This commit is contained in:
parent
a7ed28e2be
commit
245ac5c3c8
@ -121,6 +121,7 @@ target_link_libraries(cmvr_es PRIVATE
|
|||||||
cmvr_es::planner
|
cmvr_es::planner
|
||||||
cmvr_es::device::humanoid_robot
|
cmvr_es::device::humanoid_robot
|
||||||
cmvr_es::common
|
cmvr_es::common
|
||||||
|
cmvr_es::applications
|
||||||
)
|
)
|
||||||
|
|
||||||
install(TARGETS cmvr_es RUNTIME DESTINATION bin)
|
install(TARGETS cmvr_es RUNTIME DESTINATION bin)
|
||||||
|
|||||||
@ -11,5 +11,6 @@ add_subdirectory(planner)
|
|||||||
|
|
||||||
add_subdirectory(ik_solver)
|
add_subdirectory(ik_solver)
|
||||||
add_subdirectory(data_center)
|
add_subdirectory(data_center)
|
||||||
|
add_subdirectory(applications)
|
||||||
add_subdirectory(simulate)
|
add_subdirectory(simulate)
|
||||||
add_subdirectory(common)
|
add_subdirectory(common)
|
||||||
|
|||||||
25
cmvr-es/applications/CMakeLists.txt
Normal file
25
cmvr-es/applications/CMakeLists.txt
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
add_library(applications
|
||||||
|
src/touch_screen_app.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(applications PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
target_link_libraries(applications PUBLIC
|
||||||
|
cmvr_es::controller
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(cmvr_es::applications ALIAS applications)
|
||||||
|
install(TARGETS applications LIBRARY DESTINATION lib)
|
||||||
|
|
||||||
|
add_executable(touch_screen_app_test
|
||||||
|
src/touch_screen_app_test.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(touch_screen_app_test PRIVATE
|
||||||
|
cmvr_es::applications
|
||||||
|
cmvr_es::device_manager
|
||||||
|
gtest
|
||||||
|
gtest_main
|
||||||
|
pthread
|
||||||
|
glog
|
||||||
|
)
|
||||||
225
cmvr-es/applications/include/touch_screen_app.h
Normal file
225
cmvr-es/applications/include/touch_screen_app.h
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef CMVR_ES_TOUCH_SCREEN_APP_H
|
||||||
|
#define CMVR_ES_TOUCH_SCREEN_APP_H
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <Eigen/Dense>
|
||||||
|
|
||||||
|
#include "controller/include/ibvs_controller.h"
|
||||||
|
#include "devices/camera/abstract_camera.h"
|
||||||
|
#include "devices/dexhand/abstract_dexhand.h"
|
||||||
|
#include "devices/robot/abstract_robot.h"
|
||||||
|
#include "perception/include/apriltag_perception.h"
|
||||||
|
#include "perception/include/tag_relative_target_3d.h"
|
||||||
|
|
||||||
|
namespace cmvr::app {
|
||||||
|
|
||||||
|
class TouchScreenApp {
|
||||||
|
public:
|
||||||
|
enum class Phase {
|
||||||
|
IDLE = 0,
|
||||||
|
ALIGNING,
|
||||||
|
ALIGN_REACHED,
|
||||||
|
TOUCHING,
|
||||||
|
DWELLING,
|
||||||
|
RETRACTING,
|
||||||
|
DONE,
|
||||||
|
FAILED
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Status {
|
||||||
|
IDLE = 0,
|
||||||
|
NOT_INITIALIZED,
|
||||||
|
INVALID_CONFIG,
|
||||||
|
CONTROL_JOINT_MISMATCH,
|
||||||
|
ALIGN_WAITING_PERCEPTION,
|
||||||
|
ALIGN_WAITING_TRACK,
|
||||||
|
ALIGN_TARGET_SETUP_FAILED,
|
||||||
|
ALIGN_COMPUTE_FAILED,
|
||||||
|
ALIGN_TIMEOUT,
|
||||||
|
ALIGNING,
|
||||||
|
ALIGN_REACHED,
|
||||||
|
TOUCHING,
|
||||||
|
TOUCH_TRIGGERED,
|
||||||
|
TOUCH_TIMEOUT,
|
||||||
|
RETRACTING,
|
||||||
|
DONE,
|
||||||
|
STOPPED,
|
||||||
|
ROBOT_STATE_FAILED,
|
||||||
|
ROBOT_COMMAND_FAILED
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class TactileRegion {
|
||||||
|
TIP = 0,
|
||||||
|
FINGER,
|
||||||
|
PAD,
|
||||||
|
TIP_AND_FINGER,
|
||||||
|
THUMB_MIDDLE
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Options {
|
||||||
|
// IBVS / IK 初始化参数。
|
||||||
|
std::string urdf_path;
|
||||||
|
std::string base_link{"PELVIS_S"};
|
||||||
|
std::string flange_link{"R_WRIST_R_S"};
|
||||||
|
std::string camera_link;
|
||||||
|
|
||||||
|
// 视觉感知参数。
|
||||||
|
double tag_size_m{0.12};
|
||||||
|
perception::AprilTagPerception::DepthPolicy depth_policy{
|
||||||
|
perception::AprilTagPerception::DepthPolicy::NONE};
|
||||||
|
perception::TagRelativeTarget3D::TargetPointMethod target_point_method{
|
||||||
|
perception::TagRelativeTarget3D::TargetPointMethod::TAG_PLANE};
|
||||||
|
|
||||||
|
// 视觉阶段目标:触控点在相机坐标系中的 hover 位置。
|
||||||
|
Eigen::Vector3d hover_target_in_camera{0.0, 0.0, 0.12};
|
||||||
|
double target_rx{3.14159265358979323846};
|
||||||
|
double target_ry{0.0};
|
||||||
|
double target_rz{0.0};
|
||||||
|
|
||||||
|
// IBVS 参数。
|
||||||
|
double ibvs_lambda{0.7};
|
||||||
|
double ibvs_mu{0.02};
|
||||||
|
double ibvs_qdot_max{0.6};
|
||||||
|
std::array<double, 6> ibvs_vmax6{{0.4, 0.4, 0.4, 0.4, 0.4, 0.4}};
|
||||||
|
bool enable_joint_limit_avoidance{true};
|
||||||
|
double joint_limit_avoidance_gain{0.2};
|
||||||
|
double joint_limit_avoidance_margin_ratio{0.05};
|
||||||
|
double joint_limit_avoidance_max_push{0.25};
|
||||||
|
Eigen::Matrix3d R_camera_to_visp{Eigen::Matrix3d::Identity()};
|
||||||
|
Eigen::Matrix3d R_camera_to_urdf{Eigen::Matrix3d::Identity()};
|
||||||
|
|
||||||
|
// 关节控制链,默认右臂 7 轴。
|
||||||
|
std::vector<std::string> control_joint_names{
|
||||||
|
"R_SHOULDER_P", "R_SHOULDER_R", "R_SHOULDER_Y",
|
||||||
|
"R_ELBOW_R", "R_WRIST_P", "R_WRIST_Y", "R_WRIST_R"};
|
||||||
|
|
||||||
|
// 视觉对准收敛判据。
|
||||||
|
double align_xy_threshold_m{0.003};
|
||||||
|
double align_z_threshold_m{0.010};
|
||||||
|
int align_stable_frames{5};
|
||||||
|
double align_timeout_s{10.0};
|
||||||
|
bool pause_after_align_reached{false};
|
||||||
|
|
||||||
|
// 触控阶段:speedL 目标 twist(base_link 系)。
|
||||||
|
Eigen::Matrix<double, 6, 1> touch_twist_base{
|
||||||
|
(Eigen::Matrix<double, 6, 1>() << 0.0, 0.0, -0.02, 0.0, 0.0, 0.0).finished()};
|
||||||
|
double touch_acceleration{0.6};
|
||||||
|
double touch_timeout_s{2.0};
|
||||||
|
|
||||||
|
// 接触后停留与回退。
|
||||||
|
double dwell_time_s{0.05};
|
||||||
|
Eigen::Matrix<double, 6, 1> retract_twist_base{
|
||||||
|
(Eigen::Matrix<double, 6, 1>() << 0.0, 0.0, 0.03, 0.0, 0.0, 0.0).finished()};
|
||||||
|
double retract_acceleration{0.8};
|
||||||
|
double retract_duration_s{0.20};
|
||||||
|
|
||||||
|
// 指尖触觉判据。
|
||||||
|
device::FingerType tactile_finger{device::FingerType::INDEX};
|
||||||
|
TactileRegion tactile_region{TactileRegion::FINGER};
|
||||||
|
double tactile_pressure_sum_threshold{3000.0};
|
||||||
|
double tactile_pressure_peak_threshold{0.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
TouchScreenApp();
|
||||||
|
~TouchScreenApp() = default;
|
||||||
|
|
||||||
|
bool init(const std::shared_ptr<device::AbstractRobot>& robot,
|
||||||
|
const std::shared_ptr<device::AbstractDexHand>& dexhand,
|
||||||
|
const std::shared_ptr<device::AbstractCamera>& camera,
|
||||||
|
const Options& options);
|
||||||
|
|
||||||
|
void setOptions(const Options& options);
|
||||||
|
const Options& options() const { return options_; }
|
||||||
|
|
||||||
|
bool startFromPixel(int u, int v);
|
||||||
|
bool step();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
Phase phase() const { return phase_; }
|
||||||
|
Status lastStatus() const { return last_status_; }
|
||||||
|
static const char* phaseToString(Phase phase);
|
||||||
|
static const char* statusToString(Status status);
|
||||||
|
|
||||||
|
bool isBusy() const { return phase_ == Phase::ALIGNING || phase_ == Phase::ALIGN_REACHED ||
|
||||||
|
phase_ == Phase::TOUCHING || phase_ == Phase::DWELLING ||
|
||||||
|
phase_ == Phase::RETRACTING; }
|
||||||
|
bool isFinished() const { return phase_ == Phase::DONE; }
|
||||||
|
bool isFailed() const { return phase_ == Phase::FAILED; }
|
||||||
|
|
||||||
|
int targetU() const { return target_u_; }
|
||||||
|
int targetV() const { return target_v_; }
|
||||||
|
double lastTouchPressureSum() const { return last_touch_pressure_sum_; }
|
||||||
|
double lastTouchPressurePeak() const { return last_touch_pressure_peak_; }
|
||||||
|
int lastActiveTagId() const { return last_active_tag_id_; }
|
||||||
|
const Eigen::Vector3d& lastAlignErrorCamera() const { return last_align_error_camera_; }
|
||||||
|
|
||||||
|
const std::shared_ptr<perception::AprilTagPerception>& perception() const { return perception_; }
|
||||||
|
const perception::TagRelativeTarget3D& tracker() const { return tracker_; }
|
||||||
|
const IbvsController& ibvs() const { return ibvs_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
using Clock = std::chrono::steady_clock;
|
||||||
|
|
||||||
|
bool applyOptions();
|
||||||
|
bool validateControlJointNames() const;
|
||||||
|
bool stepAligning();
|
||||||
|
bool stepTouching();
|
||||||
|
bool stepDwelling();
|
||||||
|
bool stepRetracting();
|
||||||
|
|
||||||
|
bool readControlledJointPositions(std::vector<double>& q_out) const;
|
||||||
|
bool sendJointVelocity(const std::vector<double>& qdot) const;
|
||||||
|
bool sendZeroJointVelocity() const;
|
||||||
|
bool holdCurrentControlledPosition() const;
|
||||||
|
|
||||||
|
bool startTouchPhase();
|
||||||
|
bool startRetractPhase(Phase next_phase_after_retract, Status final_status_after_retract);
|
||||||
|
void enterFailed(Status status);
|
||||||
|
|
||||||
|
bool updateTouchPressure();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<device::AbstractRobot> robot_{nullptr};
|
||||||
|
std::shared_ptr<device::AbstractDexHand> dexhand_{nullptr};
|
||||||
|
std::shared_ptr<device::AbstractCamera> camera_{nullptr};
|
||||||
|
|
||||||
|
std::shared_ptr<perception::AprilTagPerception> perception_{nullptr};
|
||||||
|
perception::TagRelativeTarget3D tracker_;
|
||||||
|
IbvsController ibvs_;
|
||||||
|
Options options_{};
|
||||||
|
|
||||||
|
Phase phase_{Phase::IDLE};
|
||||||
|
Phase phase_after_retract_{Phase::DONE};
|
||||||
|
Status last_status_{Status::NOT_INITIALIZED};
|
||||||
|
|
||||||
|
bool initialized_{false};
|
||||||
|
bool target_locked_{false};
|
||||||
|
bool ibvs_target_initialized_{false};
|
||||||
|
bool touch_command_started_{false};
|
||||||
|
bool retract_command_started_{false};
|
||||||
|
|
||||||
|
int target_u_{-1};
|
||||||
|
int target_v_{-1};
|
||||||
|
int align_stable_count_{0};
|
||||||
|
int last_active_tag_id_{-1};
|
||||||
|
|
||||||
|
double last_touch_pressure_sum_{0.0};
|
||||||
|
double last_touch_pressure_peak_{0.0};
|
||||||
|
Eigen::Vector3d last_align_error_camera_{Eigen::Vector3d::Zero()};
|
||||||
|
|
||||||
|
Clock::time_point phase_start_time_{};
|
||||||
|
Status final_status_after_retract_{Status::DONE};
|
||||||
|
};
|
||||||
|
|
||||||
|
using touch_screen_app = TouchScreenApp;
|
||||||
|
|
||||||
|
} // namespace cmvr::app
|
||||||
|
|
||||||
|
#endif // CMVR_ES_TOUCH_SCREEN_APP_H
|
||||||
704
cmvr-es/applications/src/touch_screen_app.cpp
Normal file
704
cmvr-es/applications/src/touch_screen_app.cpp
Normal file
@ -0,0 +1,704 @@
|
|||||||
|
#include "applications/include/touch_screen_app.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <exception>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace cmvr::app {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::vector<double> toStdVector6(const Eigen::Matrix<double, 6, 1>& twist) {
|
||||||
|
std::vector<double> out(6, 0.0);
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
out[static_cast<size_t>(i)] = twist[i];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void accumulateMatrixStats(const std::vector<std::vector<device::TactilePoint>>& matrix,
|
||||||
|
double& sum_out,
|
||||||
|
double& peak_out) {
|
||||||
|
for (const auto& row : matrix) {
|
||||||
|
for (const auto value : row) {
|
||||||
|
const double v = static_cast<double>(value);
|
||||||
|
sum_out += v;
|
||||||
|
peak_out = std::max(peak_out, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TouchScreenApp::TouchScreenApp()
|
||||||
|
: tracker_(nullptr) {}
|
||||||
|
|
||||||
|
bool TouchScreenApp::init(const std::shared_ptr<device::AbstractRobot>& robot,
|
||||||
|
const std::shared_ptr<device::AbstractDexHand>& dexhand,
|
||||||
|
const std::shared_ptr<device::AbstractCamera>& camera,
|
||||||
|
const Options& options) {
|
||||||
|
robot_ = robot;
|
||||||
|
dexhand_ = dexhand;
|
||||||
|
camera_ = camera;
|
||||||
|
options_ = options;
|
||||||
|
|
||||||
|
if (!robot_ || !dexhand_ || !camera_) {
|
||||||
|
initialized_ = false;
|
||||||
|
last_status_ = Status::INVALID_CONFIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options_.urdf_path.empty() || options_.camera_link.empty() || options_.control_joint_names.empty()) {
|
||||||
|
initialized_ = false;
|
||||||
|
last_status_ = Status::INVALID_CONFIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
perception_ = std::make_shared<perception::AprilTagPerception>(camera_);
|
||||||
|
perception_->setTagSize(options_.tag_size_m);
|
||||||
|
|
||||||
|
tracker_.setPerception(perception_);
|
||||||
|
tracker_.setTargetPointMethod(options_.target_point_method);
|
||||||
|
|
||||||
|
if (!ibvs_.init(options_.urdf_path,
|
||||||
|
options_.base_link,
|
||||||
|
options_.flange_link,
|
||||||
|
options_.camera_link)) {
|
||||||
|
initialized_ = false;
|
||||||
|
last_status_ = Status::INVALID_CONFIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ibvs_.setPerception(perception_);
|
||||||
|
if (!validateControlJointNames()) {
|
||||||
|
initialized_ = false;
|
||||||
|
last_status_ = Status::CONTROL_JOINT_MISMATCH;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized_ = applyOptions();
|
||||||
|
if (initialized_) {
|
||||||
|
tracker_.clear();
|
||||||
|
tracker_.resetActiveTagTracking();
|
||||||
|
ibvs_.reset();
|
||||||
|
phase_ = Phase::IDLE;
|
||||||
|
phase_after_retract_ = Phase::DONE;
|
||||||
|
final_status_after_retract_ = Status::DONE;
|
||||||
|
target_locked_ = false;
|
||||||
|
ibvs_target_initialized_ = false;
|
||||||
|
touch_command_started_ = false;
|
||||||
|
retract_command_started_ = false;
|
||||||
|
align_stable_count_ = 0;
|
||||||
|
last_active_tag_id_ = -1;
|
||||||
|
last_touch_pressure_sum_ = 0.0;
|
||||||
|
last_touch_pressure_peak_ = 0.0;
|
||||||
|
last_align_error_camera_.setZero();
|
||||||
|
last_status_ = Status::IDLE;
|
||||||
|
} else {
|
||||||
|
last_status_ = Status::INVALID_CONFIG;
|
||||||
|
}
|
||||||
|
return initialized_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TouchScreenApp::setOptions(const Options& options) {
|
||||||
|
options_ = options;
|
||||||
|
if (initialized_) {
|
||||||
|
applyOptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchScreenApp::startFromPixel(int u, int v) {
|
||||||
|
if (!initialized_) {
|
||||||
|
last_status_ = Status::NOT_INITIALIZED;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (u < 0 || v < 0) {
|
||||||
|
last_status_ = Status::INVALID_CONFIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
stop();
|
||||||
|
tracker_.clear();
|
||||||
|
tracker_.resetActiveTagTracking();
|
||||||
|
ibvs_.reset();
|
||||||
|
|
||||||
|
target_u_ = u;
|
||||||
|
target_v_ = v;
|
||||||
|
target_locked_ = false;
|
||||||
|
ibvs_target_initialized_ = false;
|
||||||
|
touch_command_started_ = false;
|
||||||
|
retract_command_started_ = false;
|
||||||
|
align_stable_count_ = 0;
|
||||||
|
last_touch_pressure_sum_ = 0.0;
|
||||||
|
last_touch_pressure_peak_ = 0.0;
|
||||||
|
last_active_tag_id_ = -1;
|
||||||
|
last_align_error_camera_.setZero();
|
||||||
|
phase_ = Phase::ALIGNING;
|
||||||
|
phase_after_retract_ = Phase::DONE;
|
||||||
|
final_status_after_retract_ = Status::DONE;
|
||||||
|
phase_start_time_ = Clock::now();
|
||||||
|
last_status_ = Status::ALIGN_WAITING_TRACK;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchScreenApp::step() {
|
||||||
|
if (!initialized_) {
|
||||||
|
last_status_ = Status::NOT_INITIALIZED;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (phase_) {
|
||||||
|
case Phase::IDLE:
|
||||||
|
last_status_ = Status::IDLE;
|
||||||
|
return true;
|
||||||
|
case Phase::ALIGNING:
|
||||||
|
return stepAligning();
|
||||||
|
case Phase::ALIGN_REACHED:
|
||||||
|
last_status_ = Status::ALIGN_REACHED;
|
||||||
|
if (options_.pause_after_align_reached) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!startTouchPhase()) {
|
||||||
|
enterFailed(Status::ROBOT_COMMAND_FAILED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case Phase::TOUCHING:
|
||||||
|
return stepTouching();
|
||||||
|
case Phase::DWELLING:
|
||||||
|
return stepDwelling();
|
||||||
|
case Phase::RETRACTING:
|
||||||
|
return stepRetracting();
|
||||||
|
case Phase::DONE:
|
||||||
|
last_status_ = Status::DONE;
|
||||||
|
return true;
|
||||||
|
case Phase::FAILED:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
last_status_ = Status::INVALID_CONFIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TouchScreenApp::stop() {
|
||||||
|
if (robot_) {
|
||||||
|
try {
|
||||||
|
robot_->stopSpeedL();
|
||||||
|
} catch (...) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendZeroJointVelocity();
|
||||||
|
holdCurrentControlledPosition();
|
||||||
|
|
||||||
|
phase_ = Phase::IDLE;
|
||||||
|
phase_after_retract_ = Phase::DONE;
|
||||||
|
final_status_after_retract_ = Status::DONE;
|
||||||
|
target_locked_ = false;
|
||||||
|
ibvs_target_initialized_ = false;
|
||||||
|
touch_command_started_ = false;
|
||||||
|
retract_command_started_ = false;
|
||||||
|
align_stable_count_ = 0;
|
||||||
|
last_active_tag_id_ = -1;
|
||||||
|
last_touch_pressure_sum_ = 0.0;
|
||||||
|
last_touch_pressure_peak_ = 0.0;
|
||||||
|
last_align_error_camera_.setZero();
|
||||||
|
last_status_ = Status::STOPPED;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* TouchScreenApp::phaseToString(const Phase phase) {
|
||||||
|
switch (phase) {
|
||||||
|
case Phase::IDLE: return "IDLE";
|
||||||
|
case Phase::ALIGNING: return "ALIGNING";
|
||||||
|
case Phase::ALIGN_REACHED: return "ALIGN_REACHED";
|
||||||
|
case Phase::TOUCHING: return "TOUCHING";
|
||||||
|
case Phase::DWELLING: return "DWELLING";
|
||||||
|
case Phase::RETRACTING: return "RETRACTING";
|
||||||
|
case Phase::DONE: return "DONE";
|
||||||
|
case Phase::FAILED: return "FAILED";
|
||||||
|
}
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* TouchScreenApp::statusToString(const Status status) {
|
||||||
|
switch (status) {
|
||||||
|
case Status::IDLE: return "IDLE";
|
||||||
|
case Status::NOT_INITIALIZED: return "NOT_INITIALIZED";
|
||||||
|
case Status::INVALID_CONFIG: return "INVALID_CONFIG";
|
||||||
|
case Status::CONTROL_JOINT_MISMATCH: return "CONTROL_JOINT_MISMATCH";
|
||||||
|
case Status::ALIGN_WAITING_PERCEPTION: return "ALIGN_WAITING_PERCEPTION";
|
||||||
|
case Status::ALIGN_WAITING_TRACK: return "ALIGN_WAITING_TRACK";
|
||||||
|
case Status::ALIGN_TARGET_SETUP_FAILED: return "ALIGN_TARGET_SETUP_FAILED";
|
||||||
|
case Status::ALIGN_COMPUTE_FAILED: return "ALIGN_COMPUTE_FAILED";
|
||||||
|
case Status::ALIGN_TIMEOUT: return "ALIGN_TIMEOUT";
|
||||||
|
case Status::ALIGNING: return "ALIGNING";
|
||||||
|
case Status::ALIGN_REACHED: return "ALIGN_REACHED";
|
||||||
|
case Status::TOUCHING: return "TOUCHING";
|
||||||
|
case Status::TOUCH_TRIGGERED: return "TOUCH_TRIGGERED";
|
||||||
|
case Status::TOUCH_TIMEOUT: return "TOUCH_TIMEOUT";
|
||||||
|
case Status::RETRACTING: return "RETRACTING";
|
||||||
|
case Status::DONE: return "DONE";
|
||||||
|
case Status::STOPPED: return "STOPPED";
|
||||||
|
case Status::ROBOT_STATE_FAILED: return "ROBOT_STATE_FAILED";
|
||||||
|
case Status::ROBOT_COMMAND_FAILED: return "ROBOT_COMMAND_FAILED";
|
||||||
|
}
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchScreenApp::applyOptions() {
|
||||||
|
if (!perception_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
perception_->setTagSize(options_.tag_size_m);
|
||||||
|
tracker_.setTargetPointMethod(options_.target_point_method);
|
||||||
|
ibvs_.setLambda(options_.ibvs_lambda);
|
||||||
|
ibvs_.setMu(options_.ibvs_mu);
|
||||||
|
ibvs_.setQdotMax(options_.ibvs_qdot_max);
|
||||||
|
ibvs_.setVelocityLimit6(options_.ibvs_vmax6);
|
||||||
|
ibvs_.setJointLimitAvoidance(options_.enable_joint_limit_avoidance,
|
||||||
|
options_.joint_limit_avoidance_gain,
|
||||||
|
options_.joint_limit_avoidance_margin_ratio,
|
||||||
|
options_.joint_limit_avoidance_max_push);
|
||||||
|
ibvs_.setAlignCameraToVisp(options_.R_camera_to_visp);
|
||||||
|
ibvs_.setAlignCameraToUrdf(options_.R_camera_to_urdf);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchScreenApp::validateControlJointNames() const {
|
||||||
|
std::vector<std::string> solver_joint_names;
|
||||||
|
if (!ibvs_.getChainJointNames(solver_joint_names)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (solver_joint_names == options_.control_joint_names) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "[TouchScreenApp] control_joint_names mismatch with IbvsController IK chain\n";
|
||||||
|
std::cerr << " app joints :";
|
||||||
|
for (const auto& name : options_.control_joint_names) {
|
||||||
|
std::cerr << " " << name;
|
||||||
|
}
|
||||||
|
std::cerr << "\n solver joints:";
|
||||||
|
for (const auto& name : solver_joint_names) {
|
||||||
|
std::cerr << " " << name;
|
||||||
|
}
|
||||||
|
std::cerr << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchScreenApp::stepAligning() {
|
||||||
|
const auto now = Clock::now();
|
||||||
|
const double elapsed = std::chrono::duration<double>(now - phase_start_time_).count();
|
||||||
|
if (elapsed > options_.align_timeout_s) {
|
||||||
|
enterFailed(Status::ALIGN_TIMEOUT);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
perception::AprilTagPerception::Options perception_options;
|
||||||
|
perception_options.depth_policy = options_.depth_policy;
|
||||||
|
perception_options.detect_tags = true;
|
||||||
|
perception_options.fetch_encoded = false;
|
||||||
|
if (!perception_->update(perception_options)) {
|
||||||
|
sendZeroJointVelocity();
|
||||||
|
last_status_ = Status::ALIGN_WAITING_PERCEPTION;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tracking_ok = false;
|
||||||
|
if (!target_locked_) {
|
||||||
|
tracking_ok = tracker_.startTrackingFromPixel(target_u_, target_v_);
|
||||||
|
if (tracking_ok) {
|
||||||
|
target_locked_ = true;
|
||||||
|
ibvs_target_initialized_ = false;
|
||||||
|
align_stable_count_ = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tracking_ok = tracker_.track();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tracking_ok) {
|
||||||
|
sendZeroJointVelocity();
|
||||||
|
align_stable_count_ = 0;
|
||||||
|
last_status_ = Status::ALIGN_WAITING_TRACK;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int tag_id = tracker_.activeTagId();
|
||||||
|
last_active_tag_id_ = tag_id;
|
||||||
|
if (tag_id < 0) {
|
||||||
|
sendZeroJointVelocity();
|
||||||
|
align_stable_count_ = 0;
|
||||||
|
last_status_ = Status::ALIGN_WAITING_TRACK;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::Vector3d p_t_target = Eigen::Vector3d::Zero();
|
||||||
|
if (!tracker_.getAnchorInTag(tag_id, p_t_target)) {
|
||||||
|
sendZeroJointVelocity();
|
||||||
|
align_stable_count_ = 0;
|
||||||
|
last_status_ = Status::ALIGN_WAITING_TRACK;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ibvs_.setTrackedTagId(tag_id);
|
||||||
|
if (!ibvs_target_initialized_ || tracker_.lastSwitched()) {
|
||||||
|
if (!ibvs_.setTargetFromPointInTag(p_t_target,
|
||||||
|
options_.hover_target_in_camera,
|
||||||
|
options_.target_rx,
|
||||||
|
options_.target_ry,
|
||||||
|
options_.target_rz)) {
|
||||||
|
enterFailed(Status::ALIGN_TARGET_SETUP_FAILED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ibvs_target_initialized_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<double> q_now;
|
||||||
|
if (!readControlledJointPositions(q_now)) {
|
||||||
|
enterFailed(Status::ROBOT_STATE_FAILED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<double> qdot_cmd;
|
||||||
|
if (!ibvs_.compute(q_now, qdot_cmd)) {
|
||||||
|
switch (ibvs_.lastComputeStatus()) {
|
||||||
|
case IbvsController::ComputeStatus::NO_NEW_FRAME:
|
||||||
|
case IbvsController::ComputeStatus::NO_TAG:
|
||||||
|
case IbvsController::ComputeStatus::TAG_MISMATCH:
|
||||||
|
case IbvsController::ComputeStatus::NO_DEPTH:
|
||||||
|
sendZeroJointVelocity();
|
||||||
|
align_stable_count_ = 0;
|
||||||
|
last_status_ = Status::ALIGN_WAITING_TRACK;
|
||||||
|
return true;
|
||||||
|
case IbvsController::ComputeStatus::OK:
|
||||||
|
case IbvsController::ComputeStatus::NOT_READY:
|
||||||
|
case IbvsController::ComputeStatus::BAD_IMAGE:
|
||||||
|
case IbvsController::ComputeStatus::INVALID_INPUT:
|
||||||
|
case IbvsController::ComputeStatus::IK_FAILED:
|
||||||
|
default:
|
||||||
|
enterFailed(Status::ALIGN_COMPUTE_FAILED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sendJointVelocity(qdot_cmd)) {
|
||||||
|
enterFailed(Status::ROBOT_COMMAND_FAILED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_align_error_camera_ = tracker_.lastTargetInCamera() - options_.hover_target_in_camera;
|
||||||
|
if (std::abs(last_align_error_camera_.x()) <= options_.align_xy_threshold_m &&
|
||||||
|
std::abs(last_align_error_camera_.y()) <= options_.align_xy_threshold_m &&
|
||||||
|
std::abs(last_align_error_camera_.z()) <= options_.align_z_threshold_m) {
|
||||||
|
++align_stable_count_;
|
||||||
|
} else {
|
||||||
|
align_stable_count_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (align_stable_count_ >= options_.align_stable_frames) {
|
||||||
|
sendZeroJointVelocity();
|
||||||
|
phase_ = Phase::ALIGN_REACHED;
|
||||||
|
phase_start_time_ = Clock::now();
|
||||||
|
touch_command_started_ = false;
|
||||||
|
last_status_ = Status::ALIGN_REACHED;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_status_ = Status::ALIGNING;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchScreenApp::stepTouching() {
|
||||||
|
if (!touch_command_started_) {
|
||||||
|
if (!startTouchPhase()) {
|
||||||
|
enterFailed(Status::ROBOT_COMMAND_FAILED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTouchPressure();
|
||||||
|
if (last_touch_pressure_sum_ >= options_.tactile_pressure_sum_threshold ||
|
||||||
|
(options_.tactile_pressure_peak_threshold > 0.0 &&
|
||||||
|
last_touch_pressure_peak_ >= options_.tactile_pressure_peak_threshold)) {
|
||||||
|
try {
|
||||||
|
robot_->stopSpeedL();
|
||||||
|
} catch (...) {
|
||||||
|
enterFailed(Status::ROBOT_COMMAND_FAILED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
phase_ = Phase::DWELLING;
|
||||||
|
phase_start_time_ = Clock::now();
|
||||||
|
last_status_ = Status::TOUCH_TRIGGERED;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double elapsed = std::chrono::duration<double>(Clock::now() - phase_start_time_).count();
|
||||||
|
if (elapsed > options_.touch_timeout_s) {
|
||||||
|
try {
|
||||||
|
robot_->stopSpeedL();
|
||||||
|
} catch (...) {
|
||||||
|
enterFailed(Status::ROBOT_COMMAND_FAILED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!startRetractPhase(Phase::FAILED, Status::TOUCH_TIMEOUT)) {
|
||||||
|
enterFailed(Status::ROBOT_COMMAND_FAILED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_status_ = Status::TOUCHING;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchScreenApp::stepDwelling() {
|
||||||
|
const double elapsed = std::chrono::duration<double>(Clock::now() - phase_start_time_).count();
|
||||||
|
if (elapsed < options_.dwell_time_s) {
|
||||||
|
last_status_ = Status::TOUCH_TRIGGERED;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!startRetractPhase(Phase::DONE, Status::DONE)) {
|
||||||
|
enterFailed(Status::ROBOT_COMMAND_FAILED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchScreenApp::stepRetracting() {
|
||||||
|
if (!retract_command_started_) {
|
||||||
|
if (!startRetractPhase(phase_after_retract_, final_status_after_retract_)) {
|
||||||
|
enterFailed(Status::ROBOT_COMMAND_FAILED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const double elapsed = std::chrono::duration<double>(Clock::now() - phase_start_time_).count();
|
||||||
|
if (elapsed < options_.retract_duration_s) {
|
||||||
|
last_status_ = Status::RETRACTING;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
robot_->stopSpeedL();
|
||||||
|
} catch (...) {
|
||||||
|
enterFailed(Status::ROBOT_COMMAND_FAILED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
holdCurrentControlledPosition();
|
||||||
|
phase_ = phase_after_retract_;
|
||||||
|
last_status_ = final_status_after_retract_;
|
||||||
|
return phase_ != Phase::FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchScreenApp::readControlledJointPositions(std::vector<double>& q_out) const {
|
||||||
|
if (!robot_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<device::JointState> states;
|
||||||
|
robot_->getJointsState(states);
|
||||||
|
std::unordered_map<std::string, double> q_map;
|
||||||
|
q_map.reserve(states.size());
|
||||||
|
for (const auto& state : states) {
|
||||||
|
q_map[state.name] = state.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
q_out.resize(options_.control_joint_names.size());
|
||||||
|
for (size_t i = 0; i < options_.control_joint_names.size(); ++i) {
|
||||||
|
const auto it = q_map.find(options_.control_joint_names[i]);
|
||||||
|
if (it == q_map.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
q_out[i] = it->second;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchScreenApp::sendJointVelocity(const std::vector<double>& qdot) const {
|
||||||
|
if (!robot_ || qdot.size() != options_.control_joint_names.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<device::JointVelocityCommand> cmd;
|
||||||
|
cmd.reserve(qdot.size());
|
||||||
|
for (size_t i = 0; i < qdot.size(); ++i) {
|
||||||
|
cmd.push_back({options_.control_joint_names[i], qdot[i]});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
robot_->speedJ(cmd);
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchScreenApp::sendZeroJointVelocity() const {
|
||||||
|
std::vector<double> zero(options_.control_joint_names.size(), 0.0);
|
||||||
|
return sendJointVelocity(zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchScreenApp::holdCurrentControlledPosition() const {
|
||||||
|
if (!robot_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<device::JointState> states;
|
||||||
|
robot_->getJointsState(states);
|
||||||
|
std::unordered_map<std::string, double> q_map;
|
||||||
|
q_map.reserve(states.size());
|
||||||
|
for (const auto& state : states) {
|
||||||
|
q_map[state.name] = state.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<device::JointPoint> joints;
|
||||||
|
joints.reserve(options_.control_joint_names.size());
|
||||||
|
for (const auto& name : options_.control_joint_names) {
|
||||||
|
const auto it = q_map.find(name);
|
||||||
|
if (it == q_map.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
joints.emplace_back(name, it->second, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
robot_->servoJ(joints, 0.02);
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchScreenApp::startTouchPhase() {
|
||||||
|
if (!robot_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!robot_->speedL(toStdVector6(options_.touch_twist_base),
|
||||||
|
options_.touch_acceleration,
|
||||||
|
0.0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
phase_ = Phase::TOUCHING;
|
||||||
|
phase_start_time_ = Clock::now();
|
||||||
|
touch_command_started_ = true;
|
||||||
|
retract_command_started_ = false;
|
||||||
|
last_status_ = Status::TOUCHING;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchScreenApp::startRetractPhase(const Phase next_phase_after_retract,
|
||||||
|
const Status final_status_after_retract) {
|
||||||
|
if (!robot_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!robot_->speedL(toStdVector6(options_.retract_twist_base),
|
||||||
|
options_.retract_acceleration,
|
||||||
|
0.0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
phase_ = Phase::RETRACTING;
|
||||||
|
phase_after_retract_ = next_phase_after_retract;
|
||||||
|
final_status_after_retract_ = final_status_after_retract;
|
||||||
|
phase_start_time_ = Clock::now();
|
||||||
|
retract_command_started_ = true;
|
||||||
|
last_status_ = Status::RETRACTING;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TouchScreenApp::enterFailed(const Status status) {
|
||||||
|
try {
|
||||||
|
if (robot_) {
|
||||||
|
robot_->stopSpeedL();
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
}
|
||||||
|
sendZeroJointVelocity();
|
||||||
|
holdCurrentControlledPosition();
|
||||||
|
phase_ = Phase::FAILED;
|
||||||
|
touch_command_started_ = false;
|
||||||
|
retract_command_started_ = false;
|
||||||
|
last_status_ = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchScreenApp::updateTouchPressure() {
|
||||||
|
if (!dexhand_) {
|
||||||
|
last_touch_pressure_sum_ = 0.0;
|
||||||
|
last_touch_pressure_peak_ = 0.0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& sensors = dexhand_->getSensorData();
|
||||||
|
double sum = 0.0;
|
||||||
|
double peak = 0.0;
|
||||||
|
|
||||||
|
auto accumulate_finger = [&](const auto& finger_sensor) {
|
||||||
|
switch (options_.tactile_region) {
|
||||||
|
case TactileRegion::TIP:
|
||||||
|
accumulateMatrixStats(finger_sensor.tip.data, sum, peak);
|
||||||
|
break;
|
||||||
|
case TactileRegion::FINGER:
|
||||||
|
accumulateMatrixStats(finger_sensor.finger.data, sum, peak);
|
||||||
|
break;
|
||||||
|
case TactileRegion::PAD:
|
||||||
|
accumulateMatrixStats(finger_sensor.pad.data, sum, peak);
|
||||||
|
break;
|
||||||
|
case TactileRegion::TIP_AND_FINGER:
|
||||||
|
accumulateMatrixStats(finger_sensor.tip.data, sum, peak);
|
||||||
|
accumulateMatrixStats(finger_sensor.finger.data, sum, peak);
|
||||||
|
break;
|
||||||
|
case TactileRegion::THUMB_MIDDLE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (options_.tactile_finger) {
|
||||||
|
case device::FingerType::PINKY:
|
||||||
|
accumulate_finger(sensors.pinky);
|
||||||
|
break;
|
||||||
|
case device::FingerType::RING:
|
||||||
|
accumulate_finger(sensors.ring);
|
||||||
|
break;
|
||||||
|
case device::FingerType::MIDDLE:
|
||||||
|
accumulate_finger(sensors.middle);
|
||||||
|
break;
|
||||||
|
case device::FingerType::INDEX:
|
||||||
|
accumulate_finger(sensors.index);
|
||||||
|
break;
|
||||||
|
case device::FingerType::THUMB:
|
||||||
|
switch (options_.tactile_region) {
|
||||||
|
case TactileRegion::TIP:
|
||||||
|
accumulateMatrixStats(sensors.thumb.tip.data, sum, peak);
|
||||||
|
break;
|
||||||
|
case TactileRegion::FINGER:
|
||||||
|
accumulateMatrixStats(sensors.thumb.finger.data, sum, peak);
|
||||||
|
break;
|
||||||
|
case TactileRegion::PAD:
|
||||||
|
accumulateMatrixStats(sensors.thumb.pad.data, sum, peak);
|
||||||
|
break;
|
||||||
|
case TactileRegion::TIP_AND_FINGER:
|
||||||
|
accumulateMatrixStats(sensors.thumb.tip.data, sum, peak);
|
||||||
|
accumulateMatrixStats(sensors.thumb.finger.data, sum, peak);
|
||||||
|
break;
|
||||||
|
case TactileRegion::THUMB_MIDDLE:
|
||||||
|
accumulateMatrixStats(sensors.thumb.middle.data, sum, peak);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_touch_pressure_sum_ = sum;
|
||||||
|
last_touch_pressure_peak_ = peak;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cmvr::app
|
||||||
112
cmvr-es/applications/src/touch_screen_app_test.cpp
Normal file
112
cmvr-es/applications/src/touch_screen_app_test.cpp
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "applications/include/touch_screen_app.h"
|
||||||
|
#include "device_manager/include/device_manager.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr const char* kConfigPath =
|
||||||
|
"/home/lgv/cmvr/0-workspace/cmvr-es/cmvr-es/common/config/cabin_robot.xml";
|
||||||
|
// 这些 id 需要与现场配置一致;保持为示例调用中的写法。
|
||||||
|
constexpr const char* kRobotId = "hc01";
|
||||||
|
constexpr const char* kDexhandId = "dexhand1";
|
||||||
|
constexpr const char* kCameraId = "cam1";
|
||||||
|
constexpr const char* kUrdfPath =
|
||||||
|
"/home/lgv/cmvr/0-workspace/cmvr-es/model/xiaoyan_description/dual_arm.urdf";
|
||||||
|
constexpr const char* kBaseLink = "PELVIS_S";
|
||||||
|
constexpr const char* kFlangeLink = "R_WRIST_R_S";
|
||||||
|
constexpr const char* kCameraLink = "R_CAM";
|
||||||
|
constexpr int kTargetU = 320;
|
||||||
|
constexpr int kTargetV = 240;
|
||||||
|
|
||||||
|
void run_touch_once(int u, int v) {
|
||||||
|
const XmlNode config(kConfigPath);
|
||||||
|
if (!config.hasChild("DeviceManager")) {
|
||||||
|
std::cerr << "DeviceManager node not found\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& dm = cmvr::device::DeviceManager::getInstance(config.getChild("DeviceManager"));
|
||||||
|
|
||||||
|
auto robot = dm.getDevice<cmvr::device::AbstractRobot>(kRobotId);
|
||||||
|
auto dexhand = dm.getDevice<cmvr::device::AbstractDexHand>(kDexhandId);
|
||||||
|
auto camera = dm.getDevice<cmvr::device::AbstractCamera>(kCameraId);
|
||||||
|
|
||||||
|
|
||||||
|
cmvr::app::TouchScreenApp app;
|
||||||
|
cmvr::app::TouchScreenApp::Options opt;
|
||||||
|
|
||||||
|
opt.urdf_path = kUrdfPath;
|
||||||
|
opt.base_link = kBaseLink;
|
||||||
|
opt.flange_link = kFlangeLink;
|
||||||
|
opt.camera_link = kCameraLink;
|
||||||
|
opt.tag_size_m = 0.02;
|
||||||
|
|
||||||
|
opt.hover_target_in_camera = Eigen::Vector3d(0.0, 0.0, 0.12);
|
||||||
|
opt.pause_after_align_reached = true;
|
||||||
|
|
||||||
|
// 只测试视觉对齐阶段:禁止进入真实下压。
|
||||||
|
opt.touch_twist_base <<
|
||||||
|
0.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0;
|
||||||
|
opt.touch_acceleration = 0.6;
|
||||||
|
|
||||||
|
opt.retract_twist_base <<
|
||||||
|
0.0, 0.0, 0.03,
|
||||||
|
0.0, 0.0, 0.0;
|
||||||
|
opt.retract_duration_s = 0.20;
|
||||||
|
|
||||||
|
opt.tactile_finger = cmvr::device::FingerType::INDEX;
|
||||||
|
opt.tactile_region = cmvr::app::TouchScreenApp::TactileRegion::FINGER;
|
||||||
|
opt.tactile_pressure_sum_threshold = 1e12;
|
||||||
|
|
||||||
|
if (!app.init(robot, dexhand, camera, opt)) {
|
||||||
|
std::cerr << "TouchScreenApp init failed\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!app.startFromPixel(u, v)) {
|
||||||
|
std::cerr << "startFromPixel failed\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (app.isBusy()) {
|
||||||
|
if (!app.step()) {
|
||||||
|
std::cerr << "touch failed, status="
|
||||||
|
<< cmvr::app::TouchScreenApp::statusToString(app.lastStatus())
|
||||||
|
<< "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::cout << "phase=" << cmvr::app::TouchScreenApp::phaseToString(app.phase())
|
||||||
|
<< ", status=" << cmvr::app::TouchScreenApp::statusToString(app.lastStatus())
|
||||||
|
<< ", active_tag=" << app.lastActiveTagId()
|
||||||
|
<< ", err_c=[" << app.lastAlignErrorCamera().x() << ", "
|
||||||
|
<< app.lastAlignErrorCamera().y() << ", "
|
||||||
|
<< app.lastAlignErrorCamera().z() << "]\n";
|
||||||
|
|
||||||
|
if (app.lastStatus() == cmvr::app::TouchScreenApp::Status::ALIGN_REACHED) {
|
||||||
|
std::cout << "align reached\n";
|
||||||
|
app.stop();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.isFinished()) {
|
||||||
|
std::cout << "touch done\n";
|
||||||
|
} else if (app.isFailed()) {
|
||||||
|
std::cout << "touch failed: "
|
||||||
|
<< cmvr::app::TouchScreenApp::statusToString(app.lastStatus())
|
||||||
|
<< "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(TouchScreenAppTest, RunTouchOnceOnRealRobot) {
|
||||||
|
run_touch_once(kTargetU, kTargetV);
|
||||||
|
}
|
||||||
@ -241,6 +241,13 @@ public:
|
|||||||
// 最近一次输出的相机 twist,位于 ViSP 相机坐标系 `c`。
|
// 最近一次输出的相机 twist,位于 ViSP 相机坐标系 `c`。
|
||||||
const Eigen::Matrix<double, 6, 1>& lastCameraTwistVisp() const { return last_v_camera_visp_; }
|
const Eigen::Matrix<double, 6, 1>& lastCameraTwistVisp() const { return last_v_camera_visp_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前 IK 链的关节名称(base->camera 顺序)。
|
||||||
|
* @param joint_names 输出关节名称列表。
|
||||||
|
* @return 已初始化且成功读取时返回 `true`。
|
||||||
|
*/
|
||||||
|
bool getChainJointNames(std::vector<std::string>& joint_names) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* @brief 核心控制链路:由感知缓存和当前关节状态计算关节速度命令。
|
* @brief 核心控制链路:由感知缓存和当前关节状态计算关节速度命令。
|
||||||
|
|||||||
@ -540,7 +540,7 @@ protected:
|
|||||||
ibvs_controller_->setDepthZGain(1.0);
|
ibvs_controller_->setDepthZGain(1.0);
|
||||||
ibvs_controller_->setVelocityLimit6(vmax6_);
|
ibvs_controller_->setVelocityLimit6(vmax6_);
|
||||||
ibvs_controller_->setTrackedTagId(tracked_tag_id_);
|
ibvs_controller_->setTrackedTagId(tracked_tag_id_);
|
||||||
ibvs_controller_->setTargetFromPointInTag(Eigen::Vector3d(0.08, 0.0, 0),
|
ibvs_controller_->setTargetFromPointInTag(Eigen::Vector3d(0.08, 0.05, 0),
|
||||||
Eigen::Vector3d(0.0, 0.0, 0.4));
|
Eigen::Vector3d(0.0, 0.0, 0.4));
|
||||||
ibvs_controller_->setJointLimitAvoidance(true, 0.2, 0.15, 0.25);
|
ibvs_controller_->setJointLimitAvoidance(true, 0.2, 0.15, 0.25);
|
||||||
|
|
||||||
|
|||||||
@ -146,6 +146,14 @@ bool IbvsController::compute(const std::vector<double>& joints_angle,
|
|||||||
return computeInternal(joints_angle, qdot_out);
|
return computeInternal(joints_angle, qdot_out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IbvsController::getChainJointNames(std::vector<std::string>& joint_names) const {
|
||||||
|
if (!dls_solver_) {
|
||||||
|
joint_names.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return dls_solver_->getChainJointNames(joint_names);
|
||||||
|
}
|
||||||
|
|
||||||
bool IbvsController::computeInternal(const std::vector<double>& joints_angle,
|
bool IbvsController::computeInternal(const std::vector<double>& joints_angle,
|
||||||
std::vector<double>& qdot_out) {
|
std::vector<double>& qdot_out) {
|
||||||
last_depth_usage_ = DepthUsage::NONE;
|
last_depth_usage_ = DepthUsage::NONE;
|
||||||
@ -284,9 +292,15 @@ bool IbvsController::computeInternal(const std::vector<double>& joints_angle,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Eigen::Map<const Eigen::VectorXd> q_chain(joints_angle.data(), static_cast<Eigen::Index>(joints_angle.size()));
|
||||||
|
Eigen::Map<const Eigen::VectorXd> qdot_vec(qdot.data(), static_cast<Eigen::Index>(qdot.size()));
|
||||||
|
const Eigen::VectorXd qdot_soft_limited = dls_solver_->applyJointSoftLimitVelocity(q_chain, qdot_vec);
|
||||||
|
|
||||||
qdot_out.resize(qdot.size());
|
qdot_out.resize(qdot.size());
|
||||||
for (size_t i = 0; i < qdot.size(); ++i) {
|
for (size_t i = 0; i < qdot.size(); ++i) {
|
||||||
qdot_out[i] = SupportFunctions::clamp(qdot[i], -qdot_max_, qdot_max_);
|
qdot_out[i] = SupportFunctions::clamp(qdot_soft_limited[static_cast<Eigen::Index>(i)],
|
||||||
|
-qdot_max_,
|
||||||
|
qdot_max_);
|
||||||
}
|
}
|
||||||
|
|
||||||
last_compute_status_ = ComputeStatus::OK;
|
last_compute_status_ = ComputeStatus::OK;
|
||||||
|
|||||||
@ -90,6 +90,22 @@ public:
|
|||||||
double damping = -1.0,
|
double damping = -1.0,
|
||||||
double qdot_abs_max = std::numeric_limits<double>::infinity());
|
double qdot_abs_max = std::numeric_limits<double>::infinity());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 对靠近关节位置限位且仍继续向外运动的关节速度做软压缩。
|
||||||
|
*
|
||||||
|
* 该接口不改变主任务的求解方式,只在得到关节速度命令后做逐轴后处理。
|
||||||
|
* 软/硬边界参数来自当前 speedL 配置中的:
|
||||||
|
* - `joint_soft_limit_margin`
|
||||||
|
* - `joint_hard_limit_margin`
|
||||||
|
* - `enable_joint_soft_limit_velocity`
|
||||||
|
*
|
||||||
|
* @param q_chain 当前链关节位置,size=chain_dof_,单位 rad。
|
||||||
|
* @param qdot_des 待后处理的关节速度命令,size=chain_v_dof_,单位 rad/s。
|
||||||
|
* @return 经过软限位速度压缩后的关节速度命令。
|
||||||
|
*/
|
||||||
|
Eigen::VectorXd applyJointSoftLimitVelocity(const Eigen::VectorXd& q_chain,
|
||||||
|
const Eigen::VectorXd& qdot_des);
|
||||||
|
|
||||||
void setJointLimitAvoidance(bool enable,
|
void setJointLimitAvoidance(bool enable,
|
||||||
double gain = 0.2,
|
double gain = 0.2,
|
||||||
double margin_ratio = 0.15,
|
double margin_ratio = 0.15,
|
||||||
@ -245,8 +261,6 @@ private:
|
|||||||
Eigen::VectorXd* q_full_out = nullptr);
|
Eigen::VectorXd* q_full_out = nullptr);
|
||||||
|
|
||||||
Eigen::VectorXd applyJointVelocityLimits(const Eigen::VectorXd& qdot_des) const;
|
Eigen::VectorXd applyJointVelocityLimits(const Eigen::VectorXd& qdot_des) const;
|
||||||
Eigen::VectorXd applyJointSoftLimitVelocity(const Eigen::VectorXd& q_chain,
|
|
||||||
const Eigen::VectorXd& qdot_des);
|
|
||||||
|
|
||||||
Eigen::VectorXd applyJointAccelerationLimits(const Eigen::VectorXd& qdot_des,
|
Eigen::VectorXd applyJointAccelerationLimits(const Eigen::VectorXd& qdot_des,
|
||||||
const Eigen::VectorXd& qdot_reference,
|
const Eigen::VectorXd& qdot_reference,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user