|
|
@@ -0,0 +1,634 @@
|
|
|
+#include "core/basic_obj_types.hpp"
|
|
|
+#include "robot/kuka/kuka_defs.h"
|
|
|
+#include "utility/bit_operations.hpp"
|
|
|
+#include "utility/config_utility.hpp"
|
|
|
+#include "utility/coro_worker.hpp"
|
|
|
+#include "utility/coro_worker_helper_func.hpp"
|
|
|
+#include "utility/coro_unique_lock.hpp"
|
|
|
+#include "utility/debug_utility.hpp"
|
|
|
+#include "utility/name_translator.hpp"
|
|
|
+#include "utility/variable_helper.hpp"
|
|
|
+#include "utility/versatile_buffer2.hpp"
|
|
|
+
|
|
|
+#include <boost/asio/ip/address.hpp>
|
|
|
+#include <boost/asio/ip/tcp.hpp>
|
|
|
+#include <boost/asio/co_spawn.hpp>
|
|
|
+#include <boost/asio/detached.hpp>
|
|
|
+#include <boost/asio/use_awaitable.hpp>
|
|
|
+#include <boost/crc.hpp>
|
|
|
+#include <boost/smart_ptr/scoped_ptr.hpp>
|
|
|
+#include <boost/system/error_code.hpp>
|
|
|
+
|
|
|
+#include <spdlog/spdlog.h>
|
|
|
+
|
|
|
+#include <unordered_map>
|
|
|
+#include <string_view>
|
|
|
+
|
|
|
+#include "utility/assert_utility.h"
|
|
|
+
|
|
|
+DEFAULT_TRISTATE_OBJ_DEF(kuka_interface);
|
|
|
+
|
|
|
+namespace sophiar {
|
|
|
+
|
|
|
+ using boost::asio::async_read;
|
|
|
+ using boost::asio::async_write;
|
|
|
+ using boost::asio::awaitable;
|
|
|
+ using boost::asio::buffer;
|
|
|
+ using boost::asio::co_spawn;
|
|
|
+ using boost::asio::detached;
|
|
|
+ using boost::asio::use_awaitable;
|
|
|
+ using boost::scoped_ptr;
|
|
|
+ using boost::system::error_code;
|
|
|
+
|
|
|
+ using namespace boost::asio::ip;
|
|
|
+ using namespace Eigen;
|
|
|
+
|
|
|
+ struct kuka_interface::impl {
|
|
|
+
|
|
|
+ using packet_size_type = uint16_t;
|
|
|
+ using packet_id_type = uint32_t;
|
|
|
+ using packet_crc_type = uint32_t;
|
|
|
+
|
|
|
+ enum class packet_type : uint8_t {
|
|
|
+ QUERY = 0x01,
|
|
|
+ CONTROL = 0x02
|
|
|
+ };
|
|
|
+
|
|
|
+ enum class query_packet_type : uint8_t {
|
|
|
+ FLANGE_POSE = 0x01,
|
|
|
+ FLANGE_FORCE_TORQUE = 0x02,
|
|
|
+ JOINT_ANGLE = 0x03,
|
|
|
+ JOINT_TORQUE = 0x04
|
|
|
+ };
|
|
|
+
|
|
|
+ enum class control_packet_type : uint8_t {
|
|
|
+ PTP = 0x01,
|
|
|
+ LIN = 0x02,
|
|
|
+ LIN_REL = 0x03,
|
|
|
+ SERVO = 0x10
|
|
|
+ };
|
|
|
+
|
|
|
+ using vector6d_type = array6_obj::value_type;
|
|
|
+
|
|
|
+ static constexpr uint16_t kuka_port = 30009;
|
|
|
+ static constexpr uint16_t servo_port = 30003;
|
|
|
+ static constexpr auto kuka_endian = boost::endian::order::big;
|
|
|
+
|
|
|
+ static constexpr uint8_t query_flange_pose_mask = 1 << 0;
|
|
|
+ static constexpr uint8_t query_joint_angle_mask = 1 << 1;
|
|
|
+ static constexpr uint8_t query_flange_force_torque_mask = 1 << 2;
|
|
|
+ static constexpr uint8_t query_joint_torque_mask = 1 << 3;
|
|
|
+
|
|
|
+ static constexpr auto hard_motion_params_size = 3 * sizeof(double)
|
|
|
+ + 3 * array7_obj::binary_length();
|
|
|
+
|
|
|
+ using control_mode_type = kuka_control_mode_type;
|
|
|
+ name_translator<kuka_control_mode_type> control_mode_translator;
|
|
|
+
|
|
|
+ array7_obj::value_type invalid_array7;
|
|
|
+
|
|
|
+ kuka_interface *q_this = nullptr;
|
|
|
+
|
|
|
+ using ip_address_type = boost::asio::ip::address;
|
|
|
+ ip_address_type kuka_ip;
|
|
|
+ scoped_ptr<tcp::socket> kuka_socket;
|
|
|
+
|
|
|
+ using packet_hanlder_type = std::function<void(const_extern_memory)>;
|
|
|
+ using handler_map_type = std::unordered_map<packet_id_type, packet_hanlder_type>;
|
|
|
+ handler_map_type handler_map;
|
|
|
+
|
|
|
+ int query_interval_ms = 8;
|
|
|
+
|
|
|
+ // output variables
|
|
|
+ variable_index_type cur_flange_pose_index = -1;
|
|
|
+ variable_index_type cur_joint_angle_index = -1;
|
|
|
+ variable_index_type cur_flange_force_index = -1, cur_flange_force_uncertainty_index = -1;
|
|
|
+ variable_index_type cur_flange_torque_index = -1, cur_flange_torque_uncertainty_index = -1;
|
|
|
+ variable_index_type cur_joint_torque_index = -1;
|
|
|
+
|
|
|
+ variable_index_type is_moving_index = -1;
|
|
|
+ variable_index_type current_control_mode_index = -1;
|
|
|
+
|
|
|
+ // input variables
|
|
|
+ variable_index_type target_flange_pose_index = -1;
|
|
|
+ variable_index_type target_joint_angle_index = -1;
|
|
|
+ variable_index_type flange_speed_limit_index = -1;
|
|
|
+ variable_index_type flange_acceleration_limit_index = -1;
|
|
|
+ variable_index_type flange_jerk_limit_index = -1;
|
|
|
+ variable_index_type joint_speed_limit_index = -1;
|
|
|
+ variable_index_type joint_acceleration_limit_index = -1;
|
|
|
+ variable_index_type joint_jerk_limit_index = -1;
|
|
|
+
|
|
|
+ // temporary output variables
|
|
|
+ transform_obj::pointer cur_flange_pose;
|
|
|
+ array7_obj::pointer cur_joint_angle;
|
|
|
+ scalarxyz_obj::pointer cur_flange_force, cur_flange_force_uncertainty;
|
|
|
+ scalarxyz_obj::pointer cur_flange_torque, cur_flange_torque_uncertainty;
|
|
|
+ array7_obj::pointer cur_joint_torque;
|
|
|
+
|
|
|
+ coro_unique_lock send_packet_lock;
|
|
|
+ coro_unique_lock hard_motion_lock;
|
|
|
+
|
|
|
+ packet_id_type last_packet_id = 0;
|
|
|
+ uint8_t cur_query_mask = 0, cur_updated_mask = 0;
|
|
|
+ control_mode_type current_control_mode;
|
|
|
+
|
|
|
+ coro_signal2 status_updated_signal;
|
|
|
+ coro_worker::pointer status_worker;
|
|
|
+ coro_worker::pointer command_worker;
|
|
|
+ coro_worker::pointer kuka_reply_worker;
|
|
|
+
|
|
|
+ static void vector6d_to_transform(const vector6d_type &vec, Isometry3d &trans) {
|
|
|
+ auto translate = Translation3d(vec[0], vec[1], vec[2]);
|
|
|
+ auto rotation = AngleAxisd(vec[3], Vector3d::UnitZ())
|
|
|
+ * AngleAxisd(vec[4], Vector3d::UnitY())
|
|
|
+ * AngleAxisd(vec[5], Vector3d::UnitX());
|
|
|
+ trans = translate * rotation;
|
|
|
+ }
|
|
|
+
|
|
|
+ static void transform_to_vector6d(const Isometry3d &trans, vector6d_type &vec) {
|
|
|
+ auto transPart = trans.translation();
|
|
|
+ for (int i = 0; i < 3; ++i) {
|
|
|
+ vec[i] = transPart(i);
|
|
|
+ }
|
|
|
+ auto rotPart = trans.rotation().eulerAngles(2, 1, 0);
|
|
|
+ for (int i = 0; i < 3; ++i) {
|
|
|
+ vec[i + 3] = rotPart(i);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ template<ReadableMemory MemType>
|
|
|
+ static packet_crc_type calc_crc32(const MemType &mem) {
|
|
|
+ auto crc32 = boost::crc_32_type{};
|
|
|
+ crc32.process_bytes(mem.data(), mem.size());
|
|
|
+ return crc32.checksum();
|
|
|
+ }
|
|
|
+
|
|
|
+ template<ReadableMemory MemType>
|
|
|
+ static dynamic_memory::pointer create_packet(const MemType &content, packet_id_type packet_id) {
|
|
|
+ packet_size_type packet_size = sizeof(packet_id_type)
|
|
|
+ + content.size()
|
|
|
+ + sizeof(packet_crc_type);
|
|
|
+ auto packet = dynamic_memory::new_instance(packet_size + sizeof(packet_size_type));
|
|
|
+ auto writer = versatile_writer<kuka_endian>(*packet);
|
|
|
+ writer << packet_size << packet_id;
|
|
|
+ writer.write_memory(content);
|
|
|
+ auto crc_content = const_extern_memory{packet->data() + sizeof(packet_size_type),
|
|
|
+ sizeof(packet_id_type) + content.size()};
|
|
|
+ writer << calc_crc32(crc_content);
|
|
|
+ assert(writer.remaining_bytes() == 0);
|
|
|
+ return std::move(packet);
|
|
|
+ }
|
|
|
+
|
|
|
+ static const_extern_memory parse_packet(const dynamic_memory &packet, packet_id_type &packet_id) {
|
|
|
+ auto reader = versatile_reader<kuka_endian>(packet);
|
|
|
+ reader >> packet_id;
|
|
|
+ auto ok_sign = reader.read_value<bool>();
|
|
|
+ if (ok_sign != 0x00) {
|
|
|
+ // TODO show log
|
|
|
+ }
|
|
|
+ auto content_size = packet.size()
|
|
|
+ - sizeof(packet_id_type)
|
|
|
+ - 1 // for the ok sign
|
|
|
+ - sizeof(packet_crc_type);
|
|
|
+ auto content = const_extern_memory{packet.data() + sizeof(packet_id_type) + 1,
|
|
|
+ content_size};
|
|
|
+ reader.manual_offset(content_size);
|
|
|
+ auto crc_receive = reader.read_value<uint32_t>();
|
|
|
+ assert(reader.empty());
|
|
|
+ auto crc_content = const_extern_memory{packet.data(),
|
|
|
+ packet.size() - sizeof(packet_crc_type)};
|
|
|
+ auto crc_calc = calc_crc32(crc_content);
|
|
|
+ assert(crc_receive == crc_calc);
|
|
|
+ return content;
|
|
|
+ }
|
|
|
+
|
|
|
+ // not used for servo communication
|
|
|
+ awaitable<void> send_kuka_packet(dynamic_memory::pointer &&packet) {
|
|
|
+ auto lock_guard = coro_lock_guard{send_packet_lock};
|
|
|
+ co_await lock_guard.coro_require_lock();
|
|
|
+ assert(kuka_socket != nullptr);
|
|
|
+ co_await async_write_memory_to(*kuka_socket, *packet);
|
|
|
+ co_return;
|
|
|
+ }
|
|
|
+
|
|
|
+ awaitable<bool> receive_and_dispatch_kuka_packet() {
|
|
|
+ assert(kuka_socket != nullptr);
|
|
|
+ auto packet_size = co_await async_read_value<kuka_endian, packet_size_type>(*kuka_socket);
|
|
|
+ auto packet = dynamic_memory::new_instance(packet_size);
|
|
|
+ co_await async_fill_memory_from(*kuka_socket, *packet);
|
|
|
+ packet_id_type packet_id;
|
|
|
+ auto content = parse_packet(*packet, packet_id);
|
|
|
+ assert(handler_map.contains(packet_id));
|
|
|
+ handler_map.at(packet_id)(content);
|
|
|
+ auto delete_count = handler_map.erase(packet_id);
|
|
|
+ assert(delete_count == 1);
|
|
|
+ co_return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ void check_and_update_status() {
|
|
|
+ assert((cur_updated_mask & (~cur_query_mask)) == 0); // ensure no extra bits are set
|
|
|
+ if (cur_updated_mask != cur_query_mask) [[likely]] return;
|
|
|
+ cur_updated_mask = 0b0000;
|
|
|
+ auto ts = current_timestamp();
|
|
|
+ try_update_variable<transform_obj>(cur_flange_pose_index, std::move(cur_flange_pose), ts);
|
|
|
+ try_update_variable<array7_obj>(cur_joint_angle_index, std::move(cur_joint_angle), ts);
|
|
|
+ try_update_variable<scalarxyz_obj>(cur_flange_force_index, std::move(cur_flange_force), ts);
|
|
|
+ try_update_variable<scalarxyz_obj>(cur_flange_force_uncertainty_index,
|
|
|
+ std::move(cur_flange_force_uncertainty), ts);
|
|
|
+ try_update_variable<scalarxyz_obj>(cur_flange_torque_index, std::move(cur_flange_torque), ts);
|
|
|
+ try_update_variable<scalarxyz_obj>(cur_flange_torque_uncertainty_index,
|
|
|
+ std::move(cur_flange_torque_uncertainty), ts);
|
|
|
+ try_update_variable<array7_obj>(cur_joint_torque_index, std::move(cur_joint_torque), ts);
|
|
|
+ status_updated_signal.try_notify_all(ts);
|
|
|
+ }
|
|
|
+
|
|
|
+ void handle_new_cur_flange_pose(const_extern_memory content) {
|
|
|
+ auto reader = versatile_reader<kuka_endian>(content);
|
|
|
+ vector6d_type pose_vec;
|
|
|
+ reader >> pose_vec;
|
|
|
+ assert(is_valid_id(cur_flange_pose_index));
|
|
|
+ cur_flange_pose = transform_obj::new_instance();
|
|
|
+ vector6d_to_transform(pose_vec, cur_flange_pose->value);
|
|
|
+ cur_updated_mask |= query_flange_pose_mask;
|
|
|
+ check_and_update_status();
|
|
|
+ }
|
|
|
+
|
|
|
+ void handle_new_cur_joint_angle(const_extern_memory content) {
|
|
|
+ auto reader = versatile_reader<kuka_endian>(content);
|
|
|
+ assert(is_valid_id(cur_joint_angle_index));
|
|
|
+ cur_joint_angle = array7_obj::new_instance();
|
|
|
+ reader >> cur_joint_angle->value;
|
|
|
+ cur_updated_mask |= query_joint_angle_mask;
|
|
|
+ check_and_update_status();
|
|
|
+ }
|
|
|
+
|
|
|
+ void handle_new_cur_flange_force_torque(const_extern_memory content) {
|
|
|
+ scalarxyz_obj::value_type ignored_value;
|
|
|
+ auto reader = versatile_reader<kuka_endian>(content);
|
|
|
+
|
|
|
+#define SIMPLE_CASE(item) \
|
|
|
+ if (is_valid_id(item##_index)) { \
|
|
|
+ item = scalarxyz_obj ::new_instance(); \
|
|
|
+ reader >> item->value; \
|
|
|
+ } else { \
|
|
|
+ reader >> ignored_value; \
|
|
|
+ }
|
|
|
+
|
|
|
+ SIMPLE_CASE(cur_flange_force)
|
|
|
+ SIMPLE_CASE(cur_flange_torque)
|
|
|
+ SIMPLE_CASE(cur_flange_force_uncertainty)
|
|
|
+ SIMPLE_CASE(cur_flange_torque_uncertainty)
|
|
|
+
|
|
|
+#undef SIMPLE_CASE
|
|
|
+
|
|
|
+ cur_updated_mask |= query_flange_force_torque_mask;
|
|
|
+ check_and_update_status();
|
|
|
+ }
|
|
|
+
|
|
|
+ void handle_new_cur_joint_torque(const_extern_memory content) {
|
|
|
+ auto reader = versatile_reader<kuka_endian>(content);
|
|
|
+ assert(is_valid_id(cur_joint_torque_index));
|
|
|
+ cur_joint_torque = array7_obj::new_instance();
|
|
|
+ reader >> cur_joint_torque->value;
|
|
|
+ cur_updated_mask |= query_joint_torque_mask;
|
|
|
+ check_and_update_status();
|
|
|
+ }
|
|
|
+
|
|
|
+ void create_query_worker() {
|
|
|
+ // content defs
|
|
|
+ auto query_flange_pose_content = static_memory<2>{
|
|
|
+ (char) packet_type::QUERY,
|
|
|
+ (char) query_packet_type::FLANGE_POSE
|
|
|
+ };
|
|
|
+ auto query_joint_angle_content = static_memory<2>{
|
|
|
+ (char) packet_type::QUERY,
|
|
|
+ (char) query_packet_type::JOINT_ANGLE
|
|
|
+ };
|
|
|
+ auto query_flange_force_torque_content = static_memory<2>{
|
|
|
+ (char) packet_type::QUERY,
|
|
|
+ (char) query_packet_type::FLANGE_FORCE_TORQUE
|
|
|
+ };
|
|
|
+ auto query_joint_torque_content = static_memory<2>{
|
|
|
+ (char) packet_type::QUERY,
|
|
|
+ (char) query_packet_type::JOINT_TORQUE
|
|
|
+ };
|
|
|
+
|
|
|
+ auto enable_query_flange_pose = is_valid_id(cur_flange_pose_index);
|
|
|
+ auto enable_query_joint_angle = is_valid_id(cur_joint_angle_index);
|
|
|
+ auto enable_query_flange_force_torque = is_valid_id(cur_flange_force_index)
|
|
|
+ || is_valid_id(cur_flange_torque_index);
|
|
|
+ auto enable_query_joint_torque = is_valid_id(cur_joint_torque_index);
|
|
|
+
|
|
|
+ cur_query_mask = 0b0000;
|
|
|
+ cur_updated_mask = 0b0000;
|
|
|
+ if (enable_query_flange_pose) cur_query_mask |= query_flange_pose_mask;
|
|
|
+ if (enable_query_joint_angle) cur_query_mask |= query_joint_angle_mask;
|
|
|
+ if (enable_query_flange_force_torque) cur_query_mask |= query_flange_force_torque_mask;
|
|
|
+ if (enable_query_joint_torque) cur_query_mask |= query_joint_torque_mask;
|
|
|
+
|
|
|
+ if (cur_query_mask == 0) return;
|
|
|
+
|
|
|
+ auto worker_func = [=, this,
|
|
|
+ reply_watcher = status_updated_signal.new_watcher()]() mutable
|
|
|
+ -> awaitable<bool> {
|
|
|
+ reply_watcher.sync();
|
|
|
+
|
|
|
+#define SIMPLE_CASE(item) \
|
|
|
+ if (enable_query_##item) { \
|
|
|
+ auto packet_id = last_packet_id++; \
|
|
|
+ handler_map.emplace(packet_id, [this](const_extern_memory content) { \
|
|
|
+ handle_new_cur_##item(content); \
|
|
|
+ }); \
|
|
|
+ auto packet = create_packet(query_##item##_content, packet_id); \
|
|
|
+ co_await send_kuka_packet(std::move(packet)); \
|
|
|
+ }
|
|
|
+
|
|
|
+ SIMPLE_CASE(flange_pose)
|
|
|
+ SIMPLE_CASE(joint_angle)
|
|
|
+ SIMPLE_CASE(flange_force_torque)
|
|
|
+ SIMPLE_CASE(joint_torque)
|
|
|
+
|
|
|
+#undef SIMPLE_CASE
|
|
|
+
|
|
|
+ co_await reply_watcher.coro_wait(false); // manually synced before
|
|
|
+ co_return true;
|
|
|
+ };
|
|
|
+ auto exit_func = reset_on_exit_func(q_this);
|
|
|
+ auto noexcept_wrapper = make_noexcept_func(std::move(worker_func), [](std::exception &) {
|
|
|
+ // TODO show log
|
|
|
+ });
|
|
|
+ assert(status_worker == nullptr);
|
|
|
+ status_worker = make_interval_coro_worker(std::chrono::milliseconds(query_interval_ms),
|
|
|
+ std::move(noexcept_wrapper), std::move(exit_func));
|
|
|
+ status_worker->run();
|
|
|
+ }
|
|
|
+
|
|
|
+ void create_kuka_reply_worker() {
|
|
|
+ auto noexcept_wrapper = make_noexcept_func([this]() -> awaitable<bool> {
|
|
|
+ return receive_and_dispatch_kuka_packet();
|
|
|
+ }, [](std::exception &) {
|
|
|
+ // TODO show log
|
|
|
+ });
|
|
|
+ assert(kuka_reply_worker == nullptr);
|
|
|
+ kuka_reply_worker = make_infinite_coro_worker(std::move(noexcept_wrapper));
|
|
|
+ kuka_reply_worker->run();
|
|
|
+ }
|
|
|
+
|
|
|
+ template<typename WriterType>
|
|
|
+ void fill_hard_motion_params(WriterType &writer) {
|
|
|
+ static constexpr auto nan_literal = std::numeric_limits<double>::quiet_NaN();
|
|
|
+ writer << try_query_variable_value<double_obj>(flange_speed_limit_index, nan_literal);
|
|
|
+ writer << try_query_variable_value<double_obj>(flange_acceleration_limit_index, nan_literal);
|
|
|
+ writer << try_query_variable_value<double_obj>(flange_jerk_limit_index, nan_literal);
|
|
|
+ writer << try_query_variable_value<array7_obj>(joint_speed_limit_index, invalid_array7);
|
|
|
+ writer << try_query_variable_value<array7_obj>(joint_acceleration_limit_index, invalid_array7);
|
|
|
+ writer << try_query_variable_value<array7_obj>(joint_jerk_limit_index, invalid_array7);
|
|
|
+ }
|
|
|
+
|
|
|
+ void handle_hard_motion_reply(const_extern_memory reply) {
|
|
|
+ assert(reply.size() == 0);
|
|
|
+ hard_motion_lock.release_lock();
|
|
|
+ try_update_variable_value<bool_obj>(is_moving_index, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ auto create_hard_flange_pose_func() {
|
|
|
+ assert(current_control_mode == control_mode_type::HARD_FLANGE_POSE);
|
|
|
+ assert(is_valid_id(target_flange_pose_index));
|
|
|
+
|
|
|
+ auto worker_func = [=, this,
|
|
|
+ flange_pose = VARIABLE_MANUAL_DELEGATE(transform_obj, target_flange_pose_index)]()mutable
|
|
|
+ -> awaitable<bool> {
|
|
|
+
|
|
|
+ co_await flange_pose.coro_wait_update();
|
|
|
+ if (flange_pose.empty()) co_return true;
|
|
|
+
|
|
|
+ co_await hard_motion_lock.coro_lock();
|
|
|
+ try_update_variable_value<bool_obj>(is_moving_index, true);
|
|
|
+ flange_pose.manual_sync();
|
|
|
+ if (flange_pose.empty()) co_return true;
|
|
|
+
|
|
|
+ static constexpr auto content_size = 2 + 6 * sizeof(double) + hard_motion_params_size;
|
|
|
+ auto content = static_memory<content_size>{};
|
|
|
+ auto writer = versatile_writer<kuka_endian>{content};
|
|
|
+ writer << (uint8_t) packet_type::CONTROL
|
|
|
+ << (uint8_t) control_packet_type::LIN;
|
|
|
+ vector6d_type pose_vec;
|
|
|
+ transform_to_vector6d(flange_pose->value, pose_vec);
|
|
|
+ writer << pose_vec;
|
|
|
+ fill_hard_motion_params(writer);
|
|
|
+ assert(writer.remaining_bytes() == 0);
|
|
|
+
|
|
|
+ auto packet_id = last_packet_id++;
|
|
|
+ handler_map.emplace(packet_id, [this](const_extern_memory reply) {
|
|
|
+ handle_hard_motion_reply(reply);
|
|
|
+ });
|
|
|
+ auto packet = create_packet(content, packet_id);
|
|
|
+ co_await send_kuka_packet(std::move(packet));
|
|
|
+ co_return true;
|
|
|
+ };
|
|
|
+ return std::move(worker_func);
|
|
|
+ };
|
|
|
+
|
|
|
+ auto create_hard_joint_angle_func() {
|
|
|
+ assert(current_control_mode == control_mode_type::HARD_JOINT_ANGLE);
|
|
|
+ assert(is_valid_id(target_joint_angle_index));
|
|
|
+
|
|
|
+ auto worker_func = [=, this,
|
|
|
+ joint_angle = VARIABLE_MANUAL_DELEGATE(array7_obj, target_joint_angle_index)]()mutable
|
|
|
+ -> awaitable<bool> {
|
|
|
+
|
|
|
+ co_await joint_angle.coro_wait_update();
|
|
|
+ if (joint_angle.empty()) co_return true;
|
|
|
+
|
|
|
+ co_await hard_motion_lock.coro_lock();
|
|
|
+ try_update_variable_value<bool_obj>(is_moving_index, true);
|
|
|
+ joint_angle.manual_sync();
|
|
|
+ if (joint_angle.empty()) co_return true;
|
|
|
+
|
|
|
+ static constexpr auto content_size = 2 + 7 * sizeof(double) + hard_motion_params_size;
|
|
|
+ auto content = static_memory<content_size>{};
|
|
|
+ auto writer = versatile_writer<kuka_endian>{content};
|
|
|
+ writer << (uint8_t) packet_type::CONTROL
|
|
|
+ << (uint8_t) control_packet_type::PTP
|
|
|
+ << joint_angle->value;
|
|
|
+ fill_hard_motion_params(writer);
|
|
|
+ assert(writer.remaining_bytes() == 0);
|
|
|
+
|
|
|
+ auto packet_id = last_packet_id++;
|
|
|
+ handler_map.emplace(packet_id, [this](const_extern_memory reply) {
|
|
|
+ handle_hard_motion_reply(reply);
|
|
|
+ });
|
|
|
+ auto packet = create_packet(content, packet_id);
|
|
|
+ co_await send_kuka_packet(std::move(packet));
|
|
|
+ co_return true;
|
|
|
+ };
|
|
|
+ return std::move(worker_func);
|
|
|
+ };
|
|
|
+
|
|
|
+ void create_command_worker() {
|
|
|
+ assert(command_worker == nullptr);
|
|
|
+ assert(current_control_mode != control_mode_type::NO_CONTROL);
|
|
|
+
|
|
|
+ auto exception_handle_func = [](std::exception &) {
|
|
|
+ // TODO show log
|
|
|
+ };
|
|
|
+
|
|
|
+ switch (current_control_mode) {
|
|
|
+ case control_mode_type::HARD_FLANGE_POSE: {
|
|
|
+ auto noexcept_wrapper = make_noexcept_func(
|
|
|
+ create_hard_flange_pose_func(), std::move(exception_handle_func));
|
|
|
+ command_worker = make_infinite_coro_worker(std::move(noexcept_wrapper));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case control_mode_type::HARD_JOINT_ANGLE: {
|
|
|
+ auto noexcept_wrapper = make_noexcept_func(
|
|
|
+ create_hard_joint_angle_func(), std::move(exception_handle_func));
|
|
|
+ command_worker = make_infinite_coro_worker(std::move(noexcept_wrapper));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ default: {
|
|
|
+ assert(false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ command_worker->run();
|
|
|
+
|
|
|
+ // set initial state
|
|
|
+ try_update_variable_value<bool_obj>(is_moving_index, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ awaitable<void> on_init_impl() {
|
|
|
+ assert(kuka_socket == nullptr);
|
|
|
+ kuka_socket.reset(new tcp::socket{*global_context});
|
|
|
+ co_await kuka_socket->async_connect({kuka_ip, kuka_port}, use_awaitable);
|
|
|
+ kuka_socket->set_option(tcp::no_delay{true}); // decrease latency
|
|
|
+ handler_map.clear(); // remove existing handlers
|
|
|
+ create_kuka_reply_worker();
|
|
|
+ co_return;
|
|
|
+ }
|
|
|
+
|
|
|
+ void on_start_impl() {
|
|
|
+ create_query_worker();
|
|
|
+ if (current_control_mode != control_mode_type::NO_CONTROL) {
|
|
|
+ create_command_worker();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void initialize_translator() {
|
|
|
+ control_mode_translator.register_item("no_control", control_mode_type::NO_CONTROL);
|
|
|
+ control_mode_translator.register_item("hard_flange_pose", control_mode_type::HARD_FLANGE_POSE);
|
|
|
+ control_mode_translator.register_item("hard_joint_angle", control_mode_type::HARD_JOINT_ANGLE);
|
|
|
+ }
|
|
|
+
|
|
|
+ void load_init_config(const nlohmann::json &config) {
|
|
|
+ kuka_ip = boost::asio::ip::make_address(LOAD_STRING_ITEM("address"));
|
|
|
+ }
|
|
|
+
|
|
|
+ void load_output_config(const nlohmann::json &config) {
|
|
|
+ auto report_frequency = LOAD_FLOAT_ITEM("report_frequency");
|
|
|
+ query_interval_ms = 1000.0 / report_frequency;
|
|
|
+ if (config.contains("joint_info")) {
|
|
|
+ const auto &joint_config = config["joint_info"];
|
|
|
+ cur_joint_angle_index = TRY_LOAD_VARIABLE_INDEX2(joint_config, array7_obj, "angle");
|
|
|
+ cur_joint_torque_index = TRY_LOAD_VARIABLE_INDEX2(joint_config, array7_obj, "torque");
|
|
|
+ }
|
|
|
+ if (config.contains("flange_info")) {
|
|
|
+ const auto &flange_config = config["flange_info"];
|
|
|
+ cur_flange_pose_index = TRY_LOAD_VARIABLE_INDEX2(flange_config, transform_obj, "pose");
|
|
|
+ cur_flange_force_index = TRY_LOAD_VARIABLE_INDEX2(flange_config, scalarxyz_obj, "force");
|
|
|
+ cur_flange_torque_index = TRY_LOAD_VARIABLE_INDEX2(flange_config, scalarxyz_obj, "torque");
|
|
|
+ cur_flange_force_uncertainty_index = TRY_LOAD_VARIABLE_INDEX2(
|
|
|
+ flange_config, scalarxyz_obj, "force_uncertainty");
|
|
|
+ cur_flange_torque_uncertainty_index = TRY_LOAD_VARIABLE_INDEX2(
|
|
|
+ flange_config, scalarxyz_obj, "torque_uncertainty");
|
|
|
+ }
|
|
|
+ is_moving_index = TRY_LOAD_VARIABLE_INDEX(bool_obj, "is_moving");
|
|
|
+ // only one variable must be exported
|
|
|
+ current_control_mode_index = LOAD_VARIABLE_INDEX(u64int_obj, "current_control_mode");
|
|
|
+ }
|
|
|
+
|
|
|
+ void load_input_config(const nlohmann::json &config) {
|
|
|
+ auto control_mode_str = LOAD_STRING_ITEM("control_mode");
|
|
|
+ current_control_mode = control_mode_translator.translate(control_mode_str);
|
|
|
+ UPDATE_VARIABLE_VAL(u64int_obj, current_control_mode_index, (uint8_t) current_control_mode);
|
|
|
+ // common variables
|
|
|
+ flange_speed_limit_index = TRY_LOAD_VARIABLE_INDEX(double_obj, "flange_speed_limit");
|
|
|
+ flange_acceleration_limit_index = TRY_LOAD_VARIABLE_INDEX(double_obj, "flange_acceleration_limit");
|
|
|
+ flange_jerk_limit_index = TRY_LOAD_VARIABLE_INDEX(double_obj, "flange_jerk_limit");
|
|
|
+ joint_speed_limit_index = TRY_LOAD_VARIABLE_INDEX(array7_obj, "joint_speed_limit");
|
|
|
+ joint_acceleration_limit_index = TRY_LOAD_VARIABLE_INDEX(array7_obj, "joint_acceleration_limit");
|
|
|
+ joint_jerk_limit_index = TRY_LOAD_VARIABLE_INDEX(array7_obj, "joint_jerk_limit");
|
|
|
+ // specific variables
|
|
|
+ switch (current_control_mode) {
|
|
|
+ case control_mode_type::HARD_FLANGE_POSE: {
|
|
|
+ target_flange_pose_index = LOAD_VARIABLE_INDEX(transform_obj, "flange_pose");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case control_mode_type::HARD_JOINT_ANGLE: {
|
|
|
+ target_joint_angle_index = LOAD_VARIABLE_INDEX(array7_obj, "joint_angle");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case control_mode_type::NO_CONTROL: {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ default: {
|
|
|
+ assert(false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ impl() {
|
|
|
+ initialize_translator();
|
|
|
+
|
|
|
+ // initialize static variables
|
|
|
+ for (auto &val: invalid_array7) {
|
|
|
+ val = std::numeric_limits<double>::quiet_NaN();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ awaitable<bool> kuka_interface::on_init(const nlohmann::json &config) noexcept {
|
|
|
+ pimpl->load_init_config(config);
|
|
|
+ try {
|
|
|
+ co_await pimpl->on_init_impl();
|
|
|
+ } catch (std::exception &e) {
|
|
|
+ SPDLOG_ERROR("Failed to init KUKA interface: {}", e.what());
|
|
|
+ co_return false;
|
|
|
+ }
|
|
|
+ co_return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ awaitable<bool> kuka_interface::on_start(const nlohmann::json &config) noexcept {
|
|
|
+ // load configs
|
|
|
+ assert(config.contains("output_config"));
|
|
|
+ pimpl->load_output_config(config["output_config"]);
|
|
|
+ assert(config.contains("input_config"));
|
|
|
+ pimpl->load_input_config(config["input_config"]);
|
|
|
+ pimpl->on_start_impl();
|
|
|
+ co_return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ awaitable<void> kuka_interface::on_stop() noexcept {
|
|
|
+ SAFE_RESET_WORKER(pimpl->status_worker)
|
|
|
+ SAFE_RESET_WORKER(pimpl->command_worker)
|
|
|
+ co_return;
|
|
|
+ }
|
|
|
+
|
|
|
+ awaitable<void> kuka_interface::on_reset() noexcept {
|
|
|
+ SAFE_RESET_WORKER(pimpl->kuka_reply_worker)
|
|
|
+ pimpl->kuka_socket.reset();
|
|
|
+ co_return;
|
|
|
+ }
|
|
|
+
|
|
|
+ kuka_interface::kuka_interface()
|
|
|
+ : pimpl(std::make_unique<impl>()) {
|
|
|
+ pimpl->q_this = this;
|
|
|
+ }
|
|
|
+
|
|
|
+ kuka_interface::~kuka_interface() = default;
|
|
|
+
|
|
|
+}
|