From fdef42ac218307d58cf8b56af9dc19f7b677b466 Mon Sep 17 00:00:00 2001 From: linbo <1034003879@qq.com> Date: Wed, 17 Jun 2026 13:52:49 +0800 Subject: [PATCH 1/9] add EYOU motor control --- .../biohead_config/biohead_config.pb.txt | 27 + .../config/camera_config/camera_config.pb.txt | 2 +- .../dexhand_config/dexhand_config.pb.txt | 6 +- .../monitor_config/monitor_config.pb.txt | 25 + .../config/robot_config/robot_config.pb.txt | 36 + .../config/server_config/server_config.pb.txt | 3 + .../config_helper/include/config_helper.h | 17 + .../config_helper/include/config_setting.h | 3 + .../config_helper/src/config_setting.cpp | 12 + .../common/utils/ffmpeg/include/FrameData.h | 53 + cmvr-es/data_center/CMakeLists.txt | 1 + cmvr-es/data_center/include/motors_info.h | 3 +- cmvr-es/data_center/src/motors_info.cpp | 148 +++ cmvr-es/device_manager/src/device_factory.cpp | 37 + cmvr-es/device_manager/src/device_manager.cpp | 43 +- cmvr-es/devices/biohead/abstract_biohead.h | 1 + .../biohead_esp32/include/biohead_esp32.h | 5 +- .../biohead_esp32/src/biohead_esp32.cpp | 213 +++ cmvr-es/devices/canbus/abstract_canbus.h | 1 + .../socket/socket_can_client_raw.cc | 11 + .../can_client/socket/socket_can_client_raw.h | 4 + cmvr-es/devices/motor/CMakeLists.txt | 2 +- cmvr-es/devices/motor/abstract_motor.h | 1 + .../devices/motor/eyou_motor/CMakeLists.txt | 21 + .../ethercat/eyou_motor_ethercat_protocol.cpp | 747 +++++++++++ .../ethercat/eyou_motor_ethercat_protocol.h | 125 ++ .../ethercat/protocol/ec_master.cpp | 1143 +++++++++++++++++ .../eyou_motor/ethercat/protocol/ec_master.h | 328 +++++ .../ethercat/protocol/ec_pdo_config.h | 75 ++ .../eyou_motor/ethercat/protocol/ec_types.h | 111 ++ .../ethercat/protocol/ec_xml_parser.cpp | 317 +++++ .../ethercat/protocol/ec_xml_parser.h | 41 + .../devices/motor/eyou_motor/eyou_motor.cpp | 5 + cmvr-es/devices/motor/eyou_motor/eyou_motor.h | 46 + .../devices/motor/motor_protocol_interface.h | 3 +- cmvr-es/devices/motor/ti5_motor/ti5_motor.h | 12 +- cmvr-es/devices/robot/abstract_robot.h | 1 + .../humanoid_robot/include/humanoid_robot.h | 9 +- .../humanoid_robot/src/humanoid_robot.cpp | 81 ++ .../src/humanoid_robot_test.cpp | 18 + cmvr-es/main.cpp | 1 + cmvr-es/monitor/abstract_monitor.h | 1 + .../diskmonitor/include/disk_monitor.h | 6 +- .../monitor/diskmonitor/src/disk_monitor.cpp | 64 +- .../monitor_manager/include/monitor_factory.h | 15 +- .../monitor_manager/include/monitor_manager.h | 25 +- .../monitor_manager/src/monitor_factory.cpp | 28 +- .../monitor_manager/src/monitor_manager.cpp | 56 +- cmvr-es/service/grpc/include/server_runner.h | 7 +- .../grpc/src/grpc_humanoid_robot_service.cpp | 2 +- cmvr-es/service/grpc/src/server_runner.cpp | 26 +- .../third_party/soem/include/soem/ec_base.h | 42 + .../third_party/soem/include/soem/ec_coe.h | 73 ++ .../third_party/soem/include/soem/ec_config.h | 31 + .../x86/third_party/soem/include/soem/ec_dc.h | 27 + .../third_party/soem/include/soem/ec_eoe.h | 213 +++ .../third_party/soem/include/soem/ec_foe.h | 27 + .../third_party/soem/include/soem/ec_main.h | 624 +++++++++ .../soem/include/soem/ec_options.h | 128 ++ .../third_party/soem/include/soem/ec_print.h | 30 + .../third_party/soem/include/soem/ec_soe.h | 121 ++ .../third_party/soem/include/soem/ec_type.h | 551 ++++++++ .../third_party/soem/include/soem/nicdrv.h | 108 ++ .../x86/third_party/soem/include/soem/osal.h | 222 ++++ .../third_party/soem/include/soem/osal_defs.h | 46 + .../x86/third_party/soem/include/soem/soem.h | 28 + dependency/x86/third_party/soem/lib/libsoem.a | Bin 0 -> 691060 bytes .../biohead_config/biohead_config.proto | 44 + .../monitor_config/monitor_config.proto | 22 + .../config/robot_config/robot_config.proto | 88 ++ .../config/server_config/server_config.proto | 10 + request.txt | 1 + 72 files changed, 6303 insertions(+), 101 deletions(-) create mode 100644 cmvr-es/common/config/biohead_config/biohead_config.pb.txt create mode 100644 cmvr-es/common/config/monitor_config/monitor_config.pb.txt create mode 100644 cmvr-es/common/config/robot_config/robot_config.pb.txt create mode 100644 cmvr-es/common/config/server_config/server_config.pb.txt create mode 100644 cmvr-es/common/utils/ffmpeg/include/FrameData.h create mode 100644 cmvr-es/devices/motor/eyou_motor/CMakeLists.txt create mode 100644 cmvr-es/devices/motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.cpp create mode 100644 cmvr-es/devices/motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.h create mode 100644 cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_master.cpp create mode 100644 cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_master.h create mode 100644 cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_pdo_config.h create mode 100644 cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_types.h create mode 100644 cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_xml_parser.cpp create mode 100644 cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_xml_parser.h create mode 100644 cmvr-es/devices/motor/eyou_motor/eyou_motor.cpp create mode 100644 cmvr-es/devices/motor/eyou_motor/eyou_motor.h create mode 100644 dependency/x86/third_party/soem/include/soem/ec_base.h create mode 100644 dependency/x86/third_party/soem/include/soem/ec_coe.h create mode 100644 dependency/x86/third_party/soem/include/soem/ec_config.h create mode 100644 dependency/x86/third_party/soem/include/soem/ec_dc.h create mode 100644 dependency/x86/third_party/soem/include/soem/ec_eoe.h create mode 100644 dependency/x86/third_party/soem/include/soem/ec_foe.h create mode 100644 dependency/x86/third_party/soem/include/soem/ec_main.h create mode 100644 dependency/x86/third_party/soem/include/soem/ec_options.h create mode 100644 dependency/x86/third_party/soem/include/soem/ec_print.h create mode 100644 dependency/x86/third_party/soem/include/soem/ec_soe.h create mode 100644 dependency/x86/third_party/soem/include/soem/ec_type.h create mode 100644 dependency/x86/third_party/soem/include/soem/nicdrv.h create mode 100644 dependency/x86/third_party/soem/include/soem/osal.h create mode 100644 dependency/x86/third_party/soem/include/soem/osal_defs.h create mode 100644 dependency/x86/third_party/soem/include/soem/soem.h create mode 100644 dependency/x86/third_party/soem/lib/libsoem.a create mode 100644 protos/cmvr/config/biohead_config/biohead_config.proto create mode 100644 protos/cmvr/config/monitor_config/monitor_config.proto create mode 100644 protos/cmvr/config/robot_config/robot_config.proto create mode 100644 protos/cmvr/config/server_config/server_config.proto diff --git a/cmvr-es/common/config/biohead_config/biohead_config.pb.txt b/cmvr-es/common/config/biohead_config/biohead_config.pb.txt new file mode 100644 index 00000000..b2880e64 --- /dev/null +++ b/cmvr-es/common/config/biohead_config/biohead_config.pb.txt @@ -0,0 +1,27 @@ +bioheads { + id: "bio_head" + serial: "/dev/ttyUSB0" + ctrlFreq: 50 + enable: false + + eyebrow { + serial: "64:0~3" + offest: "90 90 90 90" + jLmtUp: "90 170 155 110" + jLmtLow: "20 77 90 20" + } + + eye { + serial: "64:4~9" + offest: "90 90 90 90 90 90" + jLmtUp: "90 150 165 90 120 115" + jLmtLow: "20 90 90 25 70 75" + } + + mouth { + serial: "65:0~9" + offest: "90 90 90 90 90 90 90 90 90 90" + jLmtUp: "150 110 130 140 100 105 110 125 90 95" + jLmtLow: "70 30 80 80 65 55 45 80 85 90" + } +} \ No newline at end of file diff --git a/cmvr-es/common/config/camera_config/camera_config.pb.txt b/cmvr-es/common/config/camera_config/camera_config.pb.txt index 18b02c30..f35f4018 100644 --- a/cmvr-es/common/config/camera_config/camera_config.pb.txt +++ b/cmvr-es/common/config/camera_config/camera_config.pb.txt @@ -27,7 +27,7 @@ realsense_cameras { align_mode: ALIGN_MODE_COLOR buffer_size: 30 sync: false - enable: true + enable: false } realsense_cameras { diff --git a/cmvr-es/common/config/dexhand_config/dexhand_config.pb.txt b/cmvr-es/common/config/dexhand_config/dexhand_config.pb.txt index e6783dd2..4a38bd2f 100644 --- a/cmvr-es/common/config/dexhand_config/dexhand_config.pb.txt +++ b/cmvr-es/common/config/dexhand_config/dexhand_config.pb.txt @@ -1,6 +1,6 @@ rh56dftp_dexhands { id: "hand1" - ip: "192.168.1.213" + ip: "192.168.1.223" port: 6000 poll_interval_ms: 10 enable: false @@ -11,7 +11,7 @@ rh56dftp_dexhands { ip: "192.168.1.224" port: 6000 poll_interval_ms: 10 - enable: true + enable: false } px_6ax_gen3 { @@ -32,5 +32,5 @@ px_6ax_gen3 { sensor_name: "Paxini Gen3末端压力" polling_read_mode: PX_6AX_GEN3_POLLING_READ_MODE_RESULTANT_FORCE auto_calibrate: false - enable: true + enable: false } diff --git a/cmvr-es/common/config/monitor_config/monitor_config.pb.txt b/cmvr-es/common/config/monitor_config/monitor_config.pb.txt new file mode 100644 index 00000000..d5350f3c --- /dev/null +++ b/cmvr-es/common/config/monitor_config/monitor_config.pb.txt @@ -0,0 +1,25 @@ +# 监控管理器配置 +monitor_manager { + disk_monitor { + id: "file_monitor" + freq: 1 + enable: false + + folders { + fileDir: "/home/share/assets/audio" + maxVolume: 1000 + } + folders { + fileDir: "/home/share/assets/image" + maxVolume: 1000 + } + folders { + fileDir: "/home/share/assets/video" + maxVolume: 1000 + } + folders { + fileDir: "../log" + maxVolume: 1000 + } + } +} \ No newline at end of file diff --git a/cmvr-es/common/config/robot_config/robot_config.pb.txt b/cmvr-es/common/config/robot_config/robot_config.pb.txt new file mode 100644 index 00000000..9e2a9327 --- /dev/null +++ b/cmvr-es/common/config/robot_config/robot_config.pb.txt @@ -0,0 +1,36 @@ +ethercat_robots { + id: "ec_robot_01" + dof: 6 + urdf: "/home/cmvr/Projects/cmvr-es/model/xiaoyan_description/dual_arm.urdf" + baseLink: "PELVIS_S" + jointNames: "L_SHOULDER_P,L_SHOULDER_R,L_SHOULDER_Y,L_ELBOW_R,L_WRIST_P,L_WRIST_Y,L_WRIST_R,R_SHOULDER_P,R_SHOULDER_R,R_SHOULDER_Y,R_ELBOW_R,R_WRIST_P,R_WRIST_Y,R_WRIST_R" + linkNames: "PELVIS_S,L_SHOULDER_P_S,L_SHOULDER_R_S,L_SHOULDER_Y_S,L_ELBOW_R_S,L_WRIST_P_S,L_WRIST_Y_S,L_WRIST_R_S,R_SHOULDER_P_S,R_SHOULDER_R_S,R_SHOULDER_Y_S,R_ELBOW_R_S,R_WRIST_P_S,R_WRIST_Y_S,R_WRIST_R_S,R_FINGER_TIP,R_CAM" + bufferSize: 100 + verbose: true + updFreq: 1000 + enable: true + + # EtherCAT主站配置 + master { + interface: "enp1s0" + cycle_time_us: 1000 + enable: true + } + + # 电机配置列表 + motors { + slave: 1 + jointName: "L_SHOULDER_P" + limitQLb: -3.14 + limitQUb: 3.14 + limitQd: 3.0 + } + + motors { + slave: 2 + jointName: "L_SHOULDER_R" + limitQLb: -3.14 + limitQUb: 3.14 + limitQd: 3.0 + } +} \ No newline at end of file diff --git a/cmvr-es/common/config/server_config/server_config.pb.txt b/cmvr-es/common/config/server_config/server_config.pb.txt new file mode 100644 index 00000000..78599a06 --- /dev/null +++ b/cmvr-es/common/config/server_config/server_config.pb.txt @@ -0,0 +1,3 @@ +grpc_server { + port: "50052" +} \ No newline at end of file diff --git a/cmvr-es/common/utils/config_helper/include/config_helper.h b/cmvr-es/common/utils/config_helper/include/config_helper.h index 7d189128..445af379 100644 --- a/cmvr-es/common/utils/config_helper/include/config_helper.h +++ b/cmvr-es/common/utils/config_helper/include/config_helper.h @@ -10,6 +10,9 @@ #include "cmvr/config/microphone_config/microphone_config.pb.h" #include "cmvr/config/speaker_config/speaker_conifg.pb.h" #include "cmvr/config/touch_screen_app_config/touch_screen_app_config.pb.h" +#include "cmvr/config/biohead_config/biohead_config.pb.h" +#include "cmvr/config/robot_config/robot_config.pb.h" +#include "cmvr/config/server_config/server_config.pb.h" #define GET_CONFIG(file, para) \ ([&]() -> bool { \ @@ -82,6 +85,20 @@ namespace cmvr return SET_CONFIG(config, touch_screen_app_config_file); } + static bool getHeadConfig(config::HeadConfig& config) + { + return GET_CONFIG(biohead_config_file, config); + } + + static bool getRobotConfig(config::RobotConfig& config) + { + return GET_CONFIG(robot_config_file, config); + } + + static bool getServerConfig(config::ServerConfig& config) + { + return GET_CONFIG(server_config_file, config); + } private: // Make macros able to call these (macros call ::cmvr::ConfigHelper::xxx) template diff --git a/cmvr-es/common/utils/config_helper/include/config_setting.h b/cmvr-es/common/utils/config_helper/include/config_setting.h index 5d58fb9e..caedf533 100644 --- a/cmvr-es/common/utils/config_helper/include/config_setting.h +++ b/cmvr-es/common/utils/config_helper/include/config_setting.h @@ -6,3 +6,6 @@ DECLARE_string(dexhand_config_file); DECLARE_string(microphone_config_file); DECLARE_string(speaker_config_file); DECLARE_string(touch_screen_app_config_file); +DECLARE_string(biohead_config_file); +DECLARE_string(robot_config_file); +DECLARE_string(server_config_file); diff --git a/cmvr-es/common/utils/config_helper/src/config_setting.cpp b/cmvr-es/common/utils/config_helper/src/config_setting.cpp index 9e44092d..528819f5 100644 --- a/cmvr-es/common/utils/config_helper/src/config_setting.cpp +++ b/cmvr-es/common/utils/config_helper/src/config_setting.cpp @@ -88,3 +88,15 @@ DEFINE_string(speaker_config_file, DEFINE_string(touch_screen_app_config_file, basePath() + "touch_screen_app_config/touch_screen_app_config.pb.txt", "The configuration file for TouchScreenApp"); + +DEFINE_string(biohead_config_file, + basePath() + "biohead_config/biohead_config.pb.txt", + "The configuration file for BioHead"); + +DEFINE_string(robot_config_file, + basePath() + "robot_config/robot_config.pb.txt", + "The configuration file for Robot"); + +DEFINE_string(server_config_file, + basePath() + "server_config/server_config.pb.txt", + "The configuration file for Server"); diff --git a/cmvr-es/common/utils/ffmpeg/include/FrameData.h b/cmvr-es/common/utils/ffmpeg/include/FrameData.h new file mode 100644 index 00000000..cd69682a --- /dev/null +++ b/cmvr-es/common/utils/ffmpeg/include/FrameData.h @@ -0,0 +1,53 @@ +// FrameData.h +#pragma once + +#include + +extern "C" { +#include +#include +} + +namespace ffmpeg { + + struct FrameData { + AVFrame* av_frame = nullptr; // 编码用帧(YUV格式) + cv::Mat rgb_image; // OpenCV用图像(BGR格式) + int64_t pts = 0; // 时间戳 + int64_t frame_index = 0; // 帧序号 + + FrameData() = default; + + ~FrameData() { + if (av_frame) { + av_frame_free(&av_frame); + } + } + + // 禁止拷贝 + FrameData(const FrameData&) = delete; + FrameData& operator=(const FrameData&) = delete; + + // 允许移动 + FrameData(FrameData&& other) noexcept + : av_frame(other.av_frame), + rgb_image(std::move(other.rgb_image)), + pts(other.pts), + frame_index(other.frame_index) { + other.av_frame = nullptr; + } + + FrameData& operator=(FrameData&& other) noexcept { + if (this != &other) { + if (av_frame) av_frame_free(&av_frame); + av_frame = other.av_frame; + rgb_image = std::move(other.rgb_image); + pts = other.pts; + frame_index = other.frame_index; + other.av_frame = nullptr; + } + return *this; + } + }; + +} // namespace ffmpeg \ No newline at end of file diff --git a/cmvr-es/data_center/CMakeLists.txt b/cmvr-es/data_center/CMakeLists.txt index d0fda580..ff66818d 100644 --- a/cmvr-es/data_center/CMakeLists.txt +++ b/cmvr-es/data_center/CMakeLists.txt @@ -11,6 +11,7 @@ target_link_libraries(data_center PRIVATE cmvr_es::utils cmvr_es::device::canbus cmvr_es::device::ti5motor + cmvr_es::device::eyoumotor protobuf glog ) diff --git a/cmvr-es/data_center/include/motors_info.h b/cmvr-es/data_center/include/motors_info.h index 56c2de04..ac892cfc 100644 --- a/cmvr-es/data_center/include/motors_info.h +++ b/cmvr-es/data_center/include/motors_info.h @@ -23,8 +23,9 @@ namespace cmvr { public: ~MotorsInfo(){}; - void init(const XmlNode &cfg); + void init(const cmvr::config::HumanRobotConfig& config); + void init(const cmvr::config::EthercatRobotConfig& config); static MotorsInfo *getInstance() { static MotorsInfo instance; diff --git a/cmvr-es/data_center/src/motors_info.cpp b/cmvr-es/data_center/src/motors_info.cpp index 4b6a087c..0a5af0b2 100644 --- a/cmvr-es/data_center/src/motors_info.cpp +++ b/cmvr-es/data_center/src/motors_info.cpp @@ -3,6 +3,8 @@ // #include "data_center/include/motors_info.h" + +#include "motor/eyou_motor/eyou_motor.h" #include "motor/ti5_motor/canopen/ti5_motor_canopen_protocol.h" #include "motor/ti5_motor/ti5_motor.h" @@ -151,6 +153,152 @@ void MotorsInfo::init(const XmlNode &cfg) { LOG(INFO) << "All enabled motors initialized successfully."; } +void MotorsInfo::init(const cmvr::config::HumanRobotConfig& config) { + const auto& can_manager = config.can_manager(); + + // 读取enable状态 + left_arm_enabled_ = can_manager.left_arm().enable(); + right_arm_enabled_ = can_manager.right_arm().enable(); + waist_enabled_ = can_manager.waist().enable(); + head_enabled_ = can_manager.head().enable(); + + // 局部变量存储电机配置 + std::vector l_motors_cfg; + std::vector r_motors_cfg; + std::vector waist_motors_cfg; + std::vector head_motors_cfg; + + // 左臂 + if (left_arm_enabled_) { + const auto& left_arm = can_manager.left_arm(); + for (const auto& motor : left_arm.motors()) { + l_motors_cfg.push_back(motor); + } + l_can_client_ = std::make_shared(left_arm); + l_can_sender_ = std::make_shared>(); + l_can_receiver_ = std::make_shared>(); + l_message_manager_ = std::make_shared>(); + } + + // 右臂 + if (right_arm_enabled_) { + const auto& right_arm = can_manager.right_arm(); + for (const auto& motor : right_arm.motors()) { + r_motors_cfg.push_back(motor); + } + r_can_client_ = std::make_shared(right_arm); + r_can_sender_ = std::make_shared>(); + r_can_receiver_ = std::make_shared>(); + r_message_manager_ = std::make_shared>(); + } + + // 腰部 + if (waist_enabled_) { + const auto& waist = can_manager.waist(); + for (const auto& motor : waist.motors()) { + waist_motors_cfg.push_back(motor); + } + waist_can_client_ = std::make_shared(waist); + waist_can_sender_ = std::make_shared>(); + waist_can_receiver_ = std::make_shared>(); + waist_message_manager_ = std::make_shared>(); + } + + // 头部 + if (head_enabled_) { + const auto& head = can_manager.head(); + for (const auto& motor : head.motors()) { + head_motors_cfg.push_back(motor); + } + head_can_client_ = std::make_shared(head); + head_can_sender_ = std::make_shared>(); + head_can_receiver_ = std::make_shared>(); + head_message_manager_ = std::make_shared>(); + } + + // 初始化 limbs + struct Limb { + std::string name; + bool enabled; + std::shared_ptr client; + std::shared_ptr> sender; + std::shared_ptr> receiver; + std::shared_ptr> message_manager; + std::vector motor_cfgs; + }; + + std::vector limbs{ + {"WAIST", waist_enabled_, waist_can_client_, waist_can_sender_, waist_can_receiver_, waist_message_manager_, waist_motors_cfg}, + {"LEFT_ARM", left_arm_enabled_, l_can_client_, l_can_sender_, l_can_receiver_, l_message_manager_, l_motors_cfg}, + {"RIGHT_ARM", right_arm_enabled_, r_can_client_, r_can_sender_, r_can_receiver_, r_message_manager_, r_motors_cfg}, + {"HEAD", head_enabled_, head_can_client_, head_can_sender_, head_can_receiver_, head_message_manager_, head_motors_cfg} + }; + + motor_manager_ = std::make_shared(); + std::vector> tasks; + + for (auto &limb : limbs) { + if (!limb.enabled) continue; + + if (limb.client) limb.client->init(); + + if (limb.sender && limb.receiver && limb.client) { + auto ret = limb.sender->Init(limb.client.get(), false); + if (ret != ErrorCode::OK) + LOG(ERROR) << "Failed to init " << limb.name << " CAN sender."; + + ret = limb.receiver->Init(limb.client.get(), limb.message_manager.get(), false); + if (ret != ErrorCode::OK) + LOG(ERROR) << "Failed to init " << limb.name << " CAN receiver."; + + limb.client->start(); + ret = limb.sender->Start(); + if (ret != ErrorCode::OK) + LOG(ERROR) << "Failed to start " << limb.name << " CAN sender."; + + ret = limb.receiver->Start(); + if (ret != ErrorCode::OK) + LOG(ERROR) << "Failed to start " << limb.name << " CAN receiver."; + } + + std::shared_ptr protocol = nullptr; + if (limb.sender && limb.message_manager) { + protocol = std::make_shared(limb.sender, limb.message_manager); + } + + if (!limb.motor_cfgs.empty()) { + tasks.push_back(std::async(std::launch::async, [this, protocol, &limb] { + LOG(INFO) << "[Thread " << std::this_thread::get_id() << "] Start initializing " << limb.name << " motors..."; + for (const auto& motor_cfg : limb.motor_cfgs) { + auto motor = std::make_shared(motor_cfg); + if (protocol) motor->setProtocol(protocol); + motor->init(); + motor_manager_->addMotor(motor); + } + })); + } + } + + for (auto& task : tasks) task.get(); + LOG(INFO) << "All enabled motors initialized successfully."; +} + + +void MotorsInfo::init(const cmvr::config::EthercatRobotConfig& config) +{ + if (!motor_manager_) + { + motor_manager_ = std::make_shared(); + } + auto protocol = std::make_shared(config.master().interface()); + for (const auto& motor_cfg : config.motors()) { + auto motor = std::make_shared(motor_cfg); + if (protocol) motor->setProtocol(protocol); + motor->init(); + motor_manager_->addMotor(motor); + } +} + bool MotorsInfo::getJointsQ(const std::vector &joints_name, std::unordered_map &joint_qs) const { auto res{true}; diff --git a/cmvr-es/device_manager/src/device_factory.cpp b/cmvr-es/device_manager/src/device_factory.cpp index fba92e51..2c136ac5 100644 --- a/cmvr-es/device_manager/src/device_factory.cpp +++ b/cmvr-es/device_manager/src/device_factory.cpp @@ -184,6 +184,10 @@ template std::shared_ptr DeviceFactory::createFromConfig DeviceFactory::createFromConfig(const config::PX6AXGen3& cfg); template std::shared_ptr DeviceFactory::createFromConfig(const config::FFMpegMicroPhoneConfig& cfg); template std::shared_ptr DeviceFactory::createFromConfig(const config::FFMpegSpeakerConfig& cfg); +template std::shared_ptr DeviceFactory::createFromConfig(const config::BioHeadConfig& cfg); +template std::shared_ptr DeviceFactory::createFromConfig(const config::HumanRobotConfig& cfg); +template std::shared_ptr DeviceFactory::createFromConfig(const config::EthercatRobotConfig& cfg); + template std::shared_ptr DeviceFactory::createFromConfig(const ConfigType& cfg) { @@ -244,6 +248,38 @@ std::shared_ptr DeviceFactory::createFromConfig(const ConfigType& cf throw runtime_error("[DeviceFactory]: Unsupported microphone device type"); } } + else if constexpr (std::is_same_v) + { + if constexpr (std::is_same_v) + { + return std::make_shared(cfg); + } + else + { + LOG(ERROR) << "[DeviceFactory]: Unsupported microphone device type "; + throw runtime_error("[DeviceFactory]: Unsupported microphone device type"); + } + } + else if constexpr (std::is_same_v) + { + if constexpr (std::is_same_v) + { + auto motos_info = MotorsInfo::getInstance(); + motos_info->init(cfg); + return std::make_shared>(cfg); + } + else if constexpr (std::is_same_v) + { + auto motos_info = MotorsInfo::getInstance(); + motos_info->init(cfg); + return std::make_shared>(cfg); + } + else + { + LOG(ERROR) << "[DeviceFactory]: Unsupported microphone device type "; + throw runtime_error("[DeviceFactory]: Unsupported microphone device type"); + } + } else { LOG(ERROR) << "[DeviceFactory]: Unsupported device type"; throw runtime_error("[DeviceFactory]: Unsupported device type"); @@ -253,3 +289,4 @@ std::shared_ptr DeviceFactory::createFromConfig(const ConfigType& cf return nullptr; } } + diff --git a/cmvr-es/device_manager/src/device_manager.cpp b/cmvr-es/device_manager/src/device_manager.cpp index 805516f9..67c06de0 100644 --- a/cmvr-es/device_manager/src/device_manager.cpp +++ b/cmvr-es/device_manager/src/device_manager.cpp @@ -312,6 +312,24 @@ void DeviceManager::init_devices_() { LOG(INFO) << "[DeviceManager]: Init Robot " << id << " Success"; } + // EYOU的先用新版的 + config::RobotConfig robot_config; + ConfigHelper::getRobotConfig(robot_config); + for (int i = 0; i < robot_config.ethercat_robots().size(); i++) { + auto robot = robot_config.ethercat_robots(i); + if (!robot.enable()) + continue; + auto id = robot.id(); + if (devices_.count(id)) { + LOG(ERROR) << "[DeviceManager]: Duplicate Robot Device ID" << id; + throw runtime_error("[DeviceManager]: Duplicate Robot Device ID" + id); + } + std::cout << "DexHand[" << i << "]: " << id << std::endl; + auto device = dev_factory_->createFromConfig(robot); + devices_[id] = device; + std::get>(devices_[id])->init(); + } + config::MicroPhoneConfig micro_phone_config; ConfigHelper::getMicroPhonesConfig(micro_phone_config); for (int i = 0; i < micro_phone_config.ffmpeg_microphones().size(); i++) { @@ -346,22 +364,21 @@ void DeviceManager::init_devices_() { std::get>(devices_[id])->init(); } - - auto biohead_node = dmgr_node.getChild("BioHead"); - for (auto &node: biohead_node.getChildren()){ - string id = node.getAttrString("id"); - if (devices_.count(id)){ - LOG(ERROR) << "[DeviceManager]: Duplicate AbstractBiohead Device ID" << id; - throw runtime_error("[DeviceManager]: Duplicate AbstractBiohead Device ID" + id); - } - auto device = dev_factory_->create(node); - if (device == nullptr){ - throw std::runtime_error("[DeviceManager]: Factory returned nullptr for device ID: " + id); + config::HeadConfig head_cfg; + ConfigHelper::getHeadConfig(head_cfg); + for (int i = 0; i < head_cfg.bioheads().size(); i++) { + auto head = head_cfg.bioheads(i); + if (!head.enable()) + continue; + auto id = head.id(); + if (devices_.count(id)) { + LOG(ERROR) << "[DeviceManager]: Duplicate Biohead Device ID" << id; + throw runtime_error("[DeviceManager]: Duplicate Biohead Device ID" + id); } + std::cout << "Biohead[" << i << "]: " << id << std::endl; + auto device = dev_factory_->createFromConfig(head); devices_[id] = device; std::get>(devices_[id])->init(); - LOG(INFO) << "[BioHead]: Init biohead " << id << " Success"; - } } catch (const exception& e) { diff --git a/cmvr-es/devices/biohead/abstract_biohead.h b/cmvr-es/devices/biohead/abstract_biohead.h index 93e0eb20..09b774ff 100644 --- a/cmvr-es/devices/biohead/abstract_biohead.h +++ b/cmvr-es/devices/biohead/abstract_biohead.h @@ -58,6 +58,7 @@ namespace cmvr::device { // 抽象头部类 class AbstractBiohead : public AbstractDevice { public: + AbstractBiohead() = default; explicit AbstractBiohead(const XmlNode &config) : AbstractDevice(config) {} ~AbstractBiohead() override = default; diff --git a/cmvr-es/devices/biohead/biohead_esp32/include/biohead_esp32.h b/cmvr-es/devices/biohead/biohead_esp32/include/biohead_esp32.h index 52b34d5d..08c0953f 100644 --- a/cmvr-es/devices/biohead/biohead_esp32/include/biohead_esp32.h +++ b/cmvr-es/devices/biohead/biohead_esp32/include/biohead_esp32.h @@ -7,7 +7,7 @@ #include #include #include - +#include "cmvr/config/biohead_config/biohead_config.pb.h" namespace cmvr::device { struct ServoChannel { @@ -18,6 +18,7 @@ namespace cmvr::device { class BioHeadRobot : public AbstractBiohead { public: explicit BioHeadRobot(const XmlNode &config); + BioHeadRobot(const cmvr::config::BioHeadConfig& config); ~BioHeadRobot() override = default; // 接口实现 @@ -66,7 +67,7 @@ namespace cmvr::device { std::shared_ptr speak_thread_; std::atomic speak_running_{false}; - + cmvr::config::BioHeadConfig config_; }; diff --git a/cmvr-es/devices/biohead/biohead_esp32/src/biohead_esp32.cpp b/cmvr-es/devices/biohead/biohead_esp32/src/biohead_esp32.cpp index c3b67887..8ff9e3b3 100644 --- a/cmvr-es/devices/biohead/biohead_esp32/src/biohead_esp32.cpp +++ b/cmvr-es/devices/biohead/biohead_esp32/src/biohead_esp32.cpp @@ -26,6 +26,219 @@ BioHeadRobot::BioHeadRobot(const XmlNode &config) : AbstractBiohead(config) { } +BioHeadRobot::BioHeadRobot(const cmvr::config::BioHeadConfig& config) : config_(config) { + // 从config中读取串口参数 + port_name_ = config_.serial(); + + serial_ = std::make_shared(); + if (!serial_->open(port_name_, 115200)) { + LOG(ERROR) << "[BioHeadRobot] Failed to open serial port: " << port_name_; + } else { + serial_->wakeupESP32(port_name_); + LOG(INFO) << "[BioHeadRobot] Serial port opened and ESP32 woken up."; + } + + // 解析眉毛配置 + if (config_.has_eyebrow()) { + const auto& eyebrow = config_.eyebrow(); + std::string serial_str = eyebrow.serial(); + std::string offset_str = eyebrow.offest(); + std::string min_angles_str = eyebrow.jlmtlow(); + std::string max_angles_str = eyebrow.jlmtup(); + + // 解析偏移角 + std::vector offsets; + std::istringstream oss(offset_str); + double val; + while (oss >> val) offsets.push_back(val); + + // 解析角度限制 + std::vector min_angles; + if (!min_angles_str.empty()) { + std::istringstream min_ss(min_angles_str); + while (min_ss >> val) min_angles.push_back(val); + } + + std::vector max_angles; + if (!max_angles_str.empty()) { + std::istringstream max_ss(max_angles_str); + while (max_ss >> val) max_angles.push_back(val); + } + + // 解析 channel,如 64:0~3 + size_t colon = serial_str.find(':'); + if (colon != std::string::npos) { + uint8_t addr = std::stoi(serial_str.substr(0, colon)); + std::string range = serial_str.substr(colon + 1); + size_t tilde = range.find('~'); + if (tilde != std::string::npos) { + int ch_start = std::stoi(range.substr(0, tilde)); + int ch_end = std::stoi(range.substr(tilde + 1)); + int channel_count = ch_end - ch_start + 1; + + // 添加舵机通道 + for (int ch = ch_start; ch <= ch_end; ++ch) { + channels_.push_back({addr, static_cast(ch)}); + } + + // 添加角度限制 + if (min_angles.size() == 1) { + min_angles_.insert(min_angles_.end(), channel_count, min_angles[0]); + } else if (min_angles.size() >= channel_count) { + min_angles_.insert(min_angles_.end(), min_angles.begin(), min_angles.begin() + channel_count); + } else { + min_angles_.insert(min_angles_.end(), channel_count, 0.0); + } + + if (max_angles.size() == 1) { + max_angles_.insert(max_angles_.end(), channel_count, max_angles[0]); + } else if (max_angles.size() >= channel_count) { + max_angles_.insert(max_angles_.end(), max_angles.begin(), max_angles.begin() + channel_count); + } else { + max_angles_.insert(max_angles_.end(), channel_count, 180.0); + } + } + } + } + + // 解析眼睛配置 + if (config_.has_eye()) { + const auto& eye = config_.eye(); + std::string serial_str = eye.serial(); + std::string offset_str = eye.offest(); + std::string min_angles_str = eye.jlmtlow(); + std::string max_angles_str = eye.jlmtup(); + + // 解析偏移角 + std::vector offsets; + std::istringstream oss(offset_str); + double val; + while (oss >> val) offsets.push_back(val); + + // 解析角度限制 + std::vector min_angles; + if (!min_angles_str.empty()) { + std::istringstream min_ss(min_angles_str); + while (min_ss >> val) min_angles.push_back(val); + } + + std::vector max_angles; + if (!max_angles_str.empty()) { + std::istringstream max_ss(max_angles_str); + while (max_ss >> val) max_angles.push_back(val); + } + + // 解析 channel,如 64:4~9 + size_t colon = serial_str.find(':'); + if (colon != std::string::npos) { + uint8_t addr = std::stoi(serial_str.substr(0, colon)); + std::string range = serial_str.substr(colon + 1); + size_t tilde = range.find('~'); + if (tilde != std::string::npos) { + int ch_start = std::stoi(range.substr(0, tilde)); + int ch_end = std::stoi(range.substr(tilde + 1)); + int channel_count = ch_end - ch_start + 1; + + // 添加舵机通道 + for (int ch = ch_start; ch <= ch_end; ++ch) { + channels_.push_back({addr, static_cast(ch)}); + } + + // 添加角度限制 + if (min_angles.size() == 1) { + min_angles_.insert(min_angles_.end(), channel_count, min_angles[0]); + } else if (min_angles.size() >= channel_count) { + min_angles_.insert(min_angles_.end(), min_angles.begin(), min_angles.begin() + channel_count); + } else { + min_angles_.insert(min_angles_.end(), channel_count, 0.0); + } + + if (max_angles.size() == 1) { + max_angles_.insert(max_angles_.end(), channel_count, max_angles[0]); + } else if (max_angles.size() >= channel_count) { + max_angles_.insert(max_angles_.end(), max_angles.begin(), max_angles.begin() + channel_count); + } else { + max_angles_.insert(max_angles_.end(), channel_count, 180.0); + } + } + } + } + + // 解析嘴巴配置 + if (config_.has_mouth()) { + const auto& mouth = config_.mouth(); + std::string serial_str = mouth.serial(); + std::string offset_str = mouth.offest(); + std::string min_angles_str = mouth.jlmtlow(); + std::string max_angles_str = mouth.jlmtup(); + + // 解析偏移角 + std::vector offsets; + std::istringstream oss(offset_str); + double val; + while (oss >> val) offsets.push_back(val); + + // 解析角度限制 + std::vector min_angles; + if (!min_angles_str.empty()) { + std::istringstream min_ss(min_angles_str); + while (min_ss >> val) min_angles.push_back(val); + } + + std::vector max_angles; + if (!max_angles_str.empty()) { + std::istringstream max_ss(max_angles_str); + while (max_ss >> val) max_angles.push_back(val); + } + + // 解析 channel,如 65:0~9 + size_t colon = serial_str.find(':'); + if (colon != std::string::npos) { + uint8_t addr = std::stoi(serial_str.substr(0, colon)); + std::string range = serial_str.substr(colon + 1); + size_t tilde = range.find('~'); + if (tilde != std::string::npos) { + int ch_start = std::stoi(range.substr(0, tilde)); + int ch_end = std::stoi(range.substr(tilde + 1)); + int channel_count = ch_end - ch_start + 1; + + // 添加舵机通道 + for (int ch = ch_start; ch <= ch_end; ++ch) { + channels_.push_back({addr, static_cast(ch)}); + } + + // 添加角度限制 + if (min_angles.size() == 1) { + min_angles_.insert(min_angles_.end(), channel_count, min_angles[0]); + } else if (min_angles.size() >= channel_count) { + min_angles_.insert(min_angles_.end(), min_angles.begin(), min_angles.begin() + channel_count); + } else { + min_angles_.insert(min_angles_.end(), channel_count, 0.0); + } + + if (max_angles.size() == 1) { + max_angles_.insert(max_angles_.end(), channel_count, max_angles[0]); + } else if (max_angles.size() >= channel_count) { + max_angles_.insert(max_angles_.end(), max_angles.begin(), max_angles.begin() + channel_count); + } else { + max_angles_.insert(max_angles_.end(), channel_count, 180.0); + } + } + } + } + + // 确保所有向量大小一致 + if (min_angles_.size() != channels_.size()) { + min_angles_.resize(channels_.size(), 0.0); + } + + if (max_angles_.size() != channels_.size()) { + max_angles_.resize(channels_.size(), 180.0); + } + + current_joints_.resize(channels_.size(), 90.0); + last_joints_ = current_joints_; +} void BioHeadRobot::parseXmlConfig(const XmlNode &node) { diff --git a/cmvr-es/devices/canbus/abstract_canbus.h b/cmvr-es/devices/canbus/abstract_canbus.h index ca0399d0..6fce7f5e 100644 --- a/cmvr-es/devices/canbus/abstract_canbus.h +++ b/cmvr-es/devices/canbus/abstract_canbus.h @@ -54,6 +54,7 @@ namespace cmvr::device { class AbstractCanbus : public AbstractDevice { public: + AbstractCanbus() = default; AbstractCanbus(const XmlNode &cfg) : AbstractDevice(cfg) {} ~AbstractCanbus() {} /** diff --git a/cmvr-es/devices/canbus/can_client/socket/socket_can_client_raw.cc b/cmvr-es/devices/canbus/can_client/socket/socket_can_client_raw.cc index aef0af2c..28779a06 100644 --- a/cmvr-es/devices/canbus/can_client/socket/socket_can_client_raw.cc +++ b/cmvr-es/devices/canbus/can_client/socket/socket_can_client_raw.cc @@ -35,6 +35,17 @@ namespace cmvr { // LOG(ERROR) << "[SocketCanClientRaw] ([SocketCanClientRaw]): Failed to parse XML: " << e.what(); } + SocketCanClientRaw::SocketCanClientRaw(const cmvr::config::CanDeviceConfig& config):config_(config) + { + auto channel_id = config_.channelid(); + port_ = static_cast(channel_id); + interface_ = CANCardParameter::NATIVE; + + enable_can_err_check_ = false; + + + } + void SocketCanClientRaw::init() { // if (!parameter.has_channel_id()) { diff --git a/cmvr-es/devices/canbus/can_client/socket/socket_can_client_raw.h b/cmvr-es/devices/canbus/can_client/socket/socket_can_client_raw.h index 3de5b42c..04d57c86 100644 --- a/cmvr-es/devices/canbus/can_client/socket/socket_can_client_raw.h +++ b/cmvr-es/devices/canbus/can_client/socket/socket_can_client_raw.h @@ -26,6 +26,7 @@ #include "gflags/gflags.h" #include "../../abstract_canbus.h" #include "canbus/common/canbus_consts.h" +#include "cmvr/config/robot_config/robot_config.pb.h" namespace cmvr { namespace device { @@ -36,6 +37,7 @@ namespace cmvr { class SocketCanClientRaw final : public AbstractCanbus { public: explicit SocketCanClientRaw(const XmlNode &cfg); + SocketCanClientRaw(const cmvr::config::CanDeviceConfig& config); ~SocketCanClientRaw(); void init() override; void start() override; @@ -73,6 +75,8 @@ namespace cmvr { // bool enable_can_err_check_{false}; + + cmvr::config::CanDeviceConfig config_; }; } } diff --git a/cmvr-es/devices/motor/CMakeLists.txt b/cmvr-es/devices/motor/CMakeLists.txt index 532924d2..3b7669c9 100644 --- a/cmvr-es/devices/motor/CMakeLists.txt +++ b/cmvr-es/devices/motor/CMakeLists.txt @@ -1,5 +1,5 @@ add_subdirectory(ti5_motor) - +add_subdirectory(eyou_motor) # -------------------------------------------------------- # Unit test # -------------------------------------------------------- diff --git a/cmvr-es/devices/motor/abstract_motor.h b/cmvr-es/devices/motor/abstract_motor.h index 710f88d2..bb89a415 100644 --- a/cmvr-es/devices/motor/abstract_motor.h +++ b/cmvr-es/devices/motor/abstract_motor.h @@ -41,6 +41,7 @@ namespace cmvr::device{ } JointStatus; public: + AbstractMotor() = default; explicit AbstractMotor(const XmlNode &config): AbstractDevice(config) {} AbstractMotor(const XmlNode &config,uint8_t node_id): AbstractDevice(config) ,node_id_(node_id) {} ~AbstractMotor() override = default; diff --git a/cmvr-es/devices/motor/eyou_motor/CMakeLists.txt b/cmvr-es/devices/motor/eyou_motor/CMakeLists.txt new file mode 100644 index 00000000..9f00953a --- /dev/null +++ b/cmvr-es/devices/motor/eyou_motor/CMakeLists.txt @@ -0,0 +1,21 @@ +add_library(eyoumotor SHARED + ${CMAKE_CURRENT_SOURCE_DIR}/ethercat/protocol/ec_xml_parser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ethercat/protocol/ec_master.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ethercat/eyou_motor_ethercat_protocol.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/eyou_motor.cpp + +) + + +target_include_directories(eyoumotor PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(cmvr_es::device::eyoumotor ALIAS eyoumotor) + +target_link_libraries(eyoumotor + PRIVATE + -lsoem + protobuf + glog +) + +install(TARGETS eyoumotor LIBRARY DESTINATION lib) \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.cpp b/cmvr-es/devices/motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.cpp new file mode 100644 index 00000000..26636505 --- /dev/null +++ b/cmvr-es/devices/motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.cpp @@ -0,0 +1,747 @@ +// +// Created by cmvr on 2026/3/30. +// + +#include "eyou_motor_ethercat_protocol.h" +#include "canbus/canopen/register.h" +#include "motor/ti5_motor/canopen/protocol/ti5_motor_tpdo1.h" +#include "motor/ti5_motor/canopen/protocol/ti5_motor_tpdo2.h" +#include "motor/ti5_motor/canopen/protocol/ti5_motor_sdo_response.h" +#include "canbus/canopen/nmt_response_protocol.h" + +#include +#include +#include + +using namespace cmvr::device; + +// 辅助函数:获取操作模式字符串 +static const char* getOpModeString(int8_t mode) { + switch (mode) { + case 1: return "Profile Position (PP)"; + case 2: return "Velocity (VL)"; + case 3: return "Profile Velocity (PV)"; + case 4: return "Profile Torque (PT)"; + case 6: return "Homing (HM)"; + case 8: return "Cyclic Sync Position (CSP)"; + case 9: return "Cyclic Sync Velocity (CSV)"; + case 10: return "Cyclic Sync Torque (CST)"; + default: return "Unknown"; + } +} + +// 构造函数 +EYouEthercatProtocol::EYouEthercatProtocol(std::string ifName, const std::string& xmlPath) + : interface_name_(ifName), + xml_config_path_(xmlPath), + initialized_(false), + slave_count_(0), + use_fixed_pdo_(xmlPath.empty()) { + + comm_proto = CommProto::ETHERCAT; + + try { + // 初始化message_manager + message_manager_ = std::make_shared>(); + + // 创建 EcMaster 实例 + master_ = std::make_unique(); + + // 初始化主站 + int cycleTimeUs = 1000; // 1ms周期 + int slaveCount = master_->init(interface_name_.c_str(), cycleTimeUs); + + if (slaveCount < 0) { + throw std::runtime_error("[EYouEthercatProtocol] master init failed"); + } + + slave_count_ = slaveCount; + + EcSlaveConfig config = createFixedSlaveConfig(1); + std::vector rxPdos = config.availableRxPdos; + std::vector txPdos = config.availableTxPdos; + + if (rxPdos.empty() || txPdos.empty()) { + LOG(ERROR) << "[EYouEthercatProtocol] No available PDOs found\n"; + throw std::runtime_error("[EYouEthercatProtocol] No available PDOs found "); + } + const EcPdo* rxPdo = &config.modules[0].rxPdos[0]; + const EcPdo* txPdo = &config.modules[0].txPdos[0]; + for (int slaveId = 1; slaveId <= slave_count_; slaveId++) { + if (master_->configurePdo(slaveId, rxPdo, txPdo) != 0) { + throw std::runtime_error("[EYouEthercatProtocol] PDO configuration failed for slave " + std::to_string(slaveId)); + } + } + master_->setupMappingAndDc(); + + // 等待配置生效 + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + // 进入OP状态 + if (master_->gotoOperational() != 0) { + throw std::runtime_error("[EYouEthercatProtocol] OPERATIONAL state failed"); + } + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + if (!master_->isOperational()) { + LOG(WARNING) << "[EYouEthercatProtocol] Master reports not operational"; + } else { + LOG(INFO) << "[EYouEthercatProtocol] EtherCAT in OPERATIONAL state"; + } + + // 初始化从站状态 + for (int i = 1; i <= slave_count_; i++) { + node_states_[i] = NodeState(); + uint16_t status = master_->getStatusWord(i); + LOG(INFO) << "[EYouEthercatProtocol] Slave " << i << " initial status word: 0x" << std::hex << status << std::dec; + } + + initialized_ = true; + LOG(INFO) << "[EYouEthercatProtocol] Initialized successfully"; + + // 添加NMT响应协议 + for (int i = 1; i <= slave_count_; i++) { + message_manager_->AddRecvProtocolData, false>(i); + } + + } catch (const std::exception& e) { + LOG(ERROR) << "[EYouEthercatProtocol] Constructor failed: " << e.what(); + throw std::runtime_error(e.what()); + } +} + +// 析构函数 +EYouEthercatProtocol::~EYouEthercatProtocol() { + if (master_) { + // 失能所有电机 + for (auto& node : node_states_) { + if (node.second.enabled) { + master_->disable(node.first); + LOG(INFO) << "[EYouEthercatProtocol] Disabled motor " << node.first; + } + } + + master_->shutdown(); + LOG(INFO) << "[EYouEthercatProtocol] Master shutdown successfully"; + } +} + +// 执行电机使能序列 +bool EYouEthercatProtocol::performEnableSequence(uint8_t node_id) { + if (node_id > slave_count_) { + LOG(ERROR) << "[EYouEthercatProtocol] Node " << node_id << " out of range"; + return false; + } + // 读取状态字 + uint16_t status = master_->getStatusWord(node_id); + LOG(INFO) << "[EYouEthercatProtocol] Node " << node_id << " status: 0x" << std::hex << status << std::dec; + + // 如果处于故障状态,先复位 + if (status & 0x0008) { + LOG(INFO) << "[EYouEthercatProtocol] Fault detected, resetting..."; + master_->faultReset(node_id); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + status = master_->getStatusWord(node_id); + } + + // 如果处于 Switch On Disabled 状态,执行使能序列 + if (status & 0x0040) { + LOG(INFO) << "[EYouEthercatProtocol] Node " << (int)node_id << " in Switch On Disabled, enabling..."; + + // 获取当前位置并同步目标位置(防止抖动) + int32_t actualPos = master_->getActualPosition(node_id); + master_->setTargetPosition(node_id, actualPos); + master_->setModeOfOperation(node_id,EcMaster::EcOperationMode_ProfilePosition); + // Shutdown + master_->setControlWord(node_id, 0x06); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Switch On + master_->setControlWord(node_id, 0x07); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Enable Operation + master_->setControlWord(node_id, 0x0F); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + status = master_->getStatusWord(node_id); + } + + // 验证使能成功 + bool enabled = (status & 0x0004) != 0; + if (enabled) { + LOG(INFO) << "[EYouEthercatProtocol] Node " << node_id << " enabled successfully"; + } else { + LOG(ERROR) << "[EYouEthercatProtocol] Node " << node_id << " enable failed, status: 0x" + << std::hex << status << std::dec; + } + + return enabled; +} + +// 操作模式转换 (msgs::RunMode -> EcOperationMode) +int8_t EYouEthercatProtocol::runModeToEcMode(msgs::RunMode mode) { + switch (mode) { + case msgs::RUN_MODE_PROFILE_POSITION: + return EcMaster::EcOperationMode_ProfilePosition; // 1 + + case msgs::RUN_MODE_PROFILE_VELOCITY: + return EcMaster::EcOperationMode_ProfileVelocity; // 3 + + case msgs::RUN_MODE_TORQUE: // 需要确认是否有这个枚举 + return EcMaster::EcOperationMode_ProfileTorque; // 4 + + case msgs::RUN_MODE_INTERPOLATED_POSITION: + return EcMaster::EcOperationMode_InterpolatedPosition; // 7 + + case msgs::RUN_MODE_CYCLIC_SYNC_POSITION: + return EcMaster::EcOperationMode_CyclicSyncPosition; // 8 + + case msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY: + return EcMaster::EcOperationMode_CyclicSyncVelocity; // 9 + + case msgs::RUN_MODE_CYCLIC_SYNC_CURRENT: // 名称统一 + return EcMaster::EcOperationMode_CyclicSyncTorque; // 10 + + default: + return 0; + } +} + +// 操作模式转换 (EcOperationMode -> msgs::RunMode) +cmvr::msgs::RunMode EYouEthercatProtocol::ecModeToRunMode(int8_t mode) { + switch (mode) { + case EcMaster::EcOperationMode_ProfilePosition: + return msgs::RUN_MODE_PROFILE_POSITION; + + case EcMaster::EcOperationMode_ProfileVelocity: + return msgs::RUN_MODE_PROFILE_VELOCITY; + + case EcMaster::EcOperationMode_ProfileTorque: + return msgs::RUN_MODE_TORQUE; // 需要确认枚举存在 + + case EcMaster::EcOperationMode_InterpolatedPosition: + return msgs::RUN_MODE_INTERPOLATED_POSITION; + + case EcMaster::EcOperationMode_CyclicSyncPosition: + return msgs::RUN_MODE_CYCLIC_SYNC_POSITION; + + case EcMaster::EcOperationMode_CyclicSyncVelocity: + return msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY; + + case EcMaster::EcOperationMode_CyclicSyncTorque: + return msgs::RUN_MODE_CYCLIC_SYNC_CURRENT; + + default: + return msgs::RUN_MODE_UNSPECIFIED; + } +} + +// 初始化节点 +bool EYouEthercatProtocol::initNode(uint8_t node_id) { + try { + if (node_id > slave_count_) { + LOG(ERROR) << "[EYouEthercatProtocol] Node " << node_id << " out of range (max: " << slave_count_ << ")"; + return false; + } + + // 执行使能序列 + if (!performEnableSequence(node_id)) { + LOG(ERROR) << "[EYouEthercatProtocol] Failed to enable node " << node_id; + return false; + } + + node_states_[node_id].initialized = true; + node_states_[node_id].enabled = true; + node_states_[node_id].current_mode = msgs::RUN_MODE_PROFILE_POSITION; + + LOG(INFO) << "[EYouEthercatProtocol] Node " << node_id << " initialized successfully"; + return true; + + } catch (const std::exception& e) { + LOG(ERROR) << "[EYouEthercatProtocol] Failed to init node " << node_id << ": " << e.what(); + return false; + } +} + +// 设置运行模式 +void EYouEthercatProtocol::setMode(uint8_t node_id, msgs::RunMode mode) { + + + int8_t ec_mode = runModeToEcMode(mode); + master_->setModeOfOperation(node_id, ec_mode); + + // 验证模式设置 + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + int8_t actual_mode = master_->getModeOfOperationDisplay(node_id); + + if (actual_mode == ec_mode) { + node_states_[node_id].current_mode = mode; + LOG(INFO) << "[EYouEthercatProtocol] Node " << (int)node_id << " mode set to " + << getOpModeString(ec_mode); + } else { + LOG(WARNING) << "[EYouEthercatProtocol] Node " << node_id << " mode set failed, " + << "expected " << getOpModeString(ec_mode) + << ", got " << getOpModeString(actual_mode); + } +} + +// 设置目标位置和速度 +void EYouEthercatProtocol::setTarget(uint8_t node_id, double angle_rad, double vel) { + auto pos_cmd = angleToPos(angle_rad); + auto speed = static_cast(std::abs(velToSpeed(vel))); + + msgs::RunMode current_mode = getMode(node_id); + + switch (current_mode) { + case msgs::RUN_MODE_PROFILE_POSITION: { + // 先设置轮廓速度 + master_->setProfileVelocity(node_id, speed); + // 设置目标位置 + master_->setTargetPosition(node_id, pos_cmd); + node_states_[node_id].target_position = pos_cmd; + + // 构建控制字:使能状态 + 立即更新标志 + uint16_t ctlword = 0x0F | 0x20; // 0x0F使能, 0x20立即更新 + + // 触发位置更新 + master_->setControlWord(node_id, ctlword); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + // 启动新位置命令 + master_->setControlWord(node_id, ctlword | 0x10); + break; + } + case msgs::RUN_MODE_CYCLIC_SYNC_POSITION: + master_->setTargetPosition(node_id, pos_cmd); + node_states_[node_id].target_position = pos_cmd; + break; + default: + LOG(WARNING) << "[EYouEthercatProtocol] setTarget(pos,vel) called in unsupported mode for node " << node_id; + break; + } +} + +// 设置目标速度 +void EYouEthercatProtocol::setTarget(uint8_t node_id, double vel) { + auto speed = static_cast(velToSpeed(vel)); + + + + msgs::RunMode current_mode = getMode(node_id); + + switch (current_mode) { + case msgs::RUN_MODE_VELOCITY: + case msgs::RUN_MODE_PROFILE_VELOCITY: + case msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY: + master_->setTargetVelocity(node_id, speed); + break; + default: + LOG(WARNING) << "[EYouEthercatProtocol] setTarget(vel) called in unsupported mode for node " << node_id; + break; + } +} + +// 设置目标位置 +void EYouEthercatProtocol::setQ(uint8_t node_id, double angle_rad) { + auto pos_cmd = angleToPos(angle_rad); + + + + msgs::RunMode current_mode = getMode(node_id); + + switch (current_mode) { + case msgs::RUN_MODE_CYCLIC_SYNC_POSITION: + { + master_->setTargetPosition(node_id, pos_cmd); + node_states_[node_id].target_position = pos_cmd; + break; + } + case msgs::RUN_MODE_PROFILE_POSITION: + { + // 1. 设置目标位置 + master_->setTargetPosition(node_id, pos_cmd); + node_states_[node_id].target_position = pos_cmd; + + // 2. 构建控制字:使能状态 + 立即更新标志 + uint16_t ctlword = 0x0F | 0x20; // 0x0F使能, 0x20立即更新 + + // 3. 触发位置更新 + master_->setControlWord(node_id, ctlword); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + // 4. 启动新位置命令 + master_->setControlWord(node_id, ctlword | 0x10); + break; + } + default: + { + LOG(WARNING) << "[EYouEthercatProtocol] setQ called in unsupported mode for node " << node_id; + break; + } + } +} + +// 设置位置限制 +void EYouEthercatProtocol::setLimitQ(uint8_t node_id, double ub, double lb) { + + + int32_t lb_val = angleToPos(lb); + int32_t ub_val = angleToPos(ub); + + // 开启软件位置限制 (0x4C494D54 = "LIMT") + uint32_t limitEnable = 0x4C494D54; + if (master_->writeSdo(node_id, 0x2003, 0x00, &limitEnable, EcDataType::Uint32, 100) != 0) { + LOG(ERROR) << "[EYouEthercatProtocol] Failed to enable software position limit for node " << node_id; + } + + // 设置软件位置限制 + if (master_->writeSdo(node_id, 0x607D, 0x01, &lb_val, EcDataType::Int32, 100) != 0) { + LOG(ERROR) << "[EYouEthercatProtocol] Failed to set lower position limit for node " << node_id; + } + + if (master_->writeSdo(node_id, 0x607D, 0x02, &ub_val, EcDataType::Int32, 100) != 0) { + LOG(ERROR) << "[EYouEthercatProtocol] Failed to set upper position limit for node " << node_id; + } + + LOG(INFO) << "[EYouEthercatProtocol] Position limits set for node " << node_id + << ": [" << lb << ", " << ub << "] rad"; +} + +// 设置速度限制 +void EYouEthercatProtocol::setLimitQd(uint8_t node_id, double qd) { + + + auto speed = static_cast(std::abs(velToSpeed(qd))); + + // 设置最大速度限制 + if (master_->writeSdo(node_id, 0x607F, 0x00, &speed, EcDataType::Uint32, 100) != 0) { + LOG(ERROR) << "[EYouEthercatProtocol] Failed to set max speed limit for node " << node_id; + } + + if (master_->writeSdo(node_id, 0x6081, 0x00, &speed, EcDataType::Uint32, 100) != 0) { + LOG(ERROR) << "[EYouEthercatProtocol] Failed to set profile speed for node " << node_id; + } + + LOG(INFO) << "[EYouEthercatProtocol] Speed limit set for node " << node_id << ": " << qd << " rad/s"; +} + +// 设置加速度限制 +void EYouEthercatProtocol::setLimitQdd(uint8_t node_id, double u_qdd, double l_qdd) { + + + // u_qdd: rad/s² (最大加速度) + // l_qdd: rad/s² (最大减速度) + // 转换为电机加速度(脉冲/秒²) + // 圈数/秒² = 弧度/秒² / 2π + // 脉冲/秒² = 圈数/秒² * GearRatio * PULSE_PER_REV + auto accel = static_cast((std::abs(u_qdd) / (2.0 * M_PI)) * GearRatio * PULSE_PER_REV); + auto decel = static_cast((std::abs(l_qdd) / (2.0 * M_PI)) * GearRatio * PULSE_PER_REV); + + // 通过SDO写入加速度和减速度限制 + if (master_->writeSdo(node_id, 0x6083, 0x00, &accel, EcDataType::Uint32, 100) != 0) { + LOG(ERROR) << "[EYouEthercatProtocol] Failed to set acceleration limit for node " << node_id; + } + + if (master_->writeSdo(node_id, 0x6084, 0x00, &decel, EcDataType::Uint32, 100) != 0) { + LOG(ERROR) << "[EYouEthercatProtocol] Failed to set deceleration limit for node " << node_id; + } + + LOG(INFO) << "[EYouEthercatProtocol] Acceleration limits set for node " << node_id + << ": accel=" << u_qdd << ", decel=" << l_qdd << " rad/s²"; +} + +// 零位校准 +bool EYouEthercatProtocol::calibrateZeroQ(uint8_t node_id) { + + + try { + int32_t pos; + int32_t offset = 0; + int32_t rev = 0; + + // 读取当前位置 + if (master_->readSdo(node_id, 0x6064, 0x00, &pos, EcDataType::Int32, 20000) != 0) { + LOG(ERROR) << "[EYouEthercatProtocol] read actual position failed"; + return false; + } + LOG(INFO) << "[EYouEthercatProtocol] Actual position: " << pos; + + // 读取home offset + if (master_->readSdo(node_id, 0x607C, 0x00, &offset, EcDataType::Int32, 20000) != 0) { + LOG(ERROR) << "[EYouEthercatProtocol] read home offset failed"; + return false; + } + LOG(INFO) << "[EYouEthercatProtocol] Home offset before: " << offset; + + // 先将home offset设为0 + offset = 0; + if (master_->writeSdo(node_id, 0x607C, 0x00, &offset, EcDataType::Int32, 20000) != 0) { + LOG(ERROR) << "[EYouEthercatProtocol] write home offset 0 failed"; + return false; + } + LOG(INFO) << "[EYouEthercatProtocol] Home offset set to 0"; + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + // 读取实际位置 + if (master_->readSdo(node_id, 0x6064, 0x00, &pos, EcDataType::Int32, 20000) != 0) { + LOG(ERROR) << "[EYouEthercatProtocol] read actual position after offset reset failed"; + return false; + } + LOG(INFO) << "[EYouEthercatProtocol] Actual position after offset reset: " << pos; + + // 获取轴转数,超圈处理 + if (master_->readSdo(node_id, 0x6091, 0x02, &rev, EcDataType::Int32, 20000) != 0) { + LOG(ERROR) << "[EYouEthercatProtocol] read gear ratio shaft revolution failed"; + return false; + } + LOG(INFO) << "[EYouEthercatProtocol] Gear ratio shaft revolution: " << rev; + + // 处理超出编码器范围的情况 + long long r = rev; + int temp = static_cast(r / 2); + if (pos > temp || pos < -temp) { + pos = pos % static_cast(r); + if (pos > temp || pos < -temp) { + if (pos > temp) { + pos = pos - static_cast(r); + } else if (pos < -temp) { + pos = pos + static_cast(r); + } + } + LOG(WARNING) << "[EYouEthercatProtocol] Position out of encoder range, adjusted to " << pos; + } + + // 写home offset偏移 + offset = -pos; + if (master_->writeSdo(node_id, 0x607C, 0x00, &offset, EcDataType::Int32, 20000) != 0) { + LOG(ERROR) << "[EYouEthercatProtocol] write home offset failed"; + return false; + } + LOG(INFO) << "[EYouEthercatProtocol] Home offset set to: " << offset; + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + // 读取实际位置 + if (master_->readSdo(node_id, 0x6064, 0x00, &pos, EcDataType::Int32, 20000) != 0) { + LOG(ERROR) << "[EYouEthercatProtocol] read actual position after calibration failed"; + return false; + } + LOG(INFO) << "[EYouEthercatProtocol] Actual position after calibration: " << pos; + + // 保存参数 + uint32_t saveValue = 0x65766173; // "evas" + if (master_->writeSdo(node_id, 0x1010, 0x01, &saveValue, EcDataType::Uint32, 20000) != 0) { + LOG(ERROR) << "[EYouEthercatProtocol] save parameters failed"; + return false; + } + LOG(INFO) << "[EYouEthercatProtocol] Parameters saved"; + + LOG(INFO) << "[EYouEthercatProtocol] Motor " << node_id << " calibrated successfully"; + return true; + + } catch (const std::exception& e) { + LOG(ERROR) << "[EYouEthercatProtocol] Calibration failed for node " << node_id << ": " << e.what(); + return false; + } +} + +// 刹车(快速停机) +void EYouEthercatProtocol::brake(uint8_t node_id) { + + + // 发送快速停机命令 + uint16_t ctlword = 0x0002; // Quick Stop + master_->setControlWord(node_id, ctlword); + + LOG(INFO) << "[EYouEthercatProtocol] Brake (quick stop) applied to motor " << node_id; +} + +// 检查是否到达目标位置 +bool EYouEthercatProtocol::reachedTargetQ(uint8_t node_id) { + uint16_t status_word = getStatusWord(node_id); + // 检查状态字的bit 10(目标到达位) + return (status_word & 0x0400) != 0; +} + +// 获取当前位置 +double EYouEthercatProtocol::getQ(uint8_t node_id) { + + int32_t position = master_->getActualPosition(node_id); + return posToAngle(position); +} + +// 获取当前速度 +double EYouEthercatProtocol::getQd(uint8_t node_id) { + + int32_t velocity = master_->getActualVelocity(node_id); + return speedToVel(velocity); +} + +// 设置速度 +void EYouEthercatProtocol::setQd(uint8_t node_id, double qd) { + auto speed = static_cast(std::abs(velToSpeed(qd))); + + + + msgs::RunMode current_mode = getMode(node_id); + + switch (current_mode) { + case msgs::RUN_MODE_CYCLIC_SYNC_POSITION: + case msgs::RUN_MODE_PROFILE_POSITION: + master_->setProfileVelocity(node_id, speed); + last_Qd_[node_id] = speed; + break; + case msgs::RUN_MODE_PROFILE_VELOCITY: + case msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY: + master_->setTargetVelocity(node_id, static_cast(speed)); + break; + default: + LOG(WARNING) << "[EYouEthercatProtocol] setQd called in unsupported mode for node " << node_id; + break; + } +} + +// 设置加速度 +void EYouEthercatProtocol::setQdd(uint8_t node_id, double qdd) { + // qdd: rad/s² (加速度) + // 转换为电机加速度(脉冲/秒²) + uint32_t accel = static_cast((std::abs(qdd) / (2.0 * M_PI)) * GearRatio * PULSE_PER_REV); + + if (last_Qdd_[node_id] != accel) { + + master_->setProfileAcceleration(node_id, accel); + master_->setProfileDeceleration(node_id, accel); + last_Qdd_[node_id] = accel; + } +} + +// 力矩关闭 +void EYouEthercatProtocol::torqueOff(uint8_t node_id) { + + auto ret = master_->disable(node_id); + node_states_[node_id].enabled = false; + LOG(INFO) << "[EYouEthercatProtocol] Torque off for motor " << node_id << ", ret = " << ret; +} + +// 获取运行模式 +cmvr::msgs::RunMode EYouEthercatProtocol::getMode(uint8_t node_id) { + + int8_t mode = master_->getModeOfOperationDisplay(node_id); + return ecModeToRunMode(mode); +} + +// 配置轮廓参数 +void EYouEthercatProtocol::configProfile(uint8_t node_id, uint32_t speed, uint32_t accel, uint32_t decel) { + + master_->setProfileVelocity(node_id, speed); + master_->setProfileAcceleration(node_id, accel); + master_->setProfileDeceleration(node_id, decel); + LOG(INFO) << "[EYouEthercatProtocol] Profile configured for node " << node_id + << ": speed=" << speed << ", accel=" << accel << ", decel=" << decel; +} + +// 获取力矩 +double EYouEthercatProtocol::getTorque(uint8_t node_id) { + + int16_t torque = master_->getActualTorque(node_id); + // 返回实际力矩值(取决于具体驱动器,这里假设是额定力矩的倍数) + return torque / 1000.0; +} + +// 获取状态字 +uint16_t EYouEthercatProtocol::getStatusWord(uint8_t node_id) { + + uint16_t status_word = master_->getStatusWord(node_id); + node_states_[node_id].status_word = status_word; + return status_word; +} + +// 检查电机是否使能 +bool EYouEthercatProtocol::isEnabled(uint8_t node_id) { + uint16_t status_word = getStatusWord(node_id); + // 检查状态字的bit 2(操作使能)和bit 3(故障) + return (status_word & 0x000C) == 0x0008; +} + +// 检查是否有故障 +bool EYouEthercatProtocol::isFault(uint8_t node_id) { + uint16_t status_word = getStatusWord(node_id); + return (status_word & 0x0008) != 0; +} + +// 故障复位 +void EYouEthercatProtocol::faultReset(uint8_t node_id) { + if (isFault(node_id)) { + + if (master_->faultReset(node_id) == 0) { + LOG(INFO) << "[EYouEthercatProtocol] Fault reset for motor " << node_id; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } else { + LOG(ERROR) << "[EYouEthercatProtocol] Failed to reset fault for motor " << node_id; + } + } +} + +// 等待目标到达 +bool EYouEthercatProtocol::waitForTargetReached(uint8_t node_id, int timeout_ms) { + return waitUntil([this, node_id]() { return reachedTargetQ(node_id); }, timeout_ms); +} + +// 获取RobotDetail数据 +std::unique_ptr EYouEthercatProtocol::GetRobotDetail() { + if (message_manager_) { + auto data_ptr = std::make_unique(); + message_manager_->GetSensorData(data_ptr.get()); + return data_ptr; + } + return std::make_unique(); +} + +// 辅助函数实现(弧度制版本) +double EYouEthercatProtocol::posToAngle(int32_t pos) const { + // 脉冲数 -> 弧度 + // 圈数 = 脉冲数 / (GearRatio * PULSE_PER_REV) + // 弧度 = 圈数 * 2π + return (pos * 2.0 * M_PI) / (GearRatio * PULSE_PER_REV); +} + +int32_t EYouEthercatProtocol::angleToPos(double angle) const { + // 弧度 -> 脉冲数 + // 圈数 = 弧度 / 2π + // 脉冲数 = 圈数 * GearRatio * PULSE_PER_REV + return static_cast((angle / (2.0 * M_PI)) * GearRatio * PULSE_PER_REV); +} + +double EYouEthercatProtocol::velToSpeed(double vel) const { + // vel: rad/s (角速度) + // 输出:电机速度(脉冲/秒) + // 圈数/秒 = vel / 2π + // 脉冲/秒 = 圈数/秒 * GearRatio * PULSE_PER_REV + return (vel / (2.0 * M_PI)) * GearRatio * PULSE_PER_REV; +} + +double EYouEthercatProtocol::speedToVel(int32_t speed) const { + // speed: 电机速度(脉冲/秒) + // 输出:rad/s + // 圈数/秒 = speed / (GearRatio * PULSE_PER_REV) + // 弧度/秒 = 圈数/秒 * 2π + return (speed * 2.0 * M_PI) / (GearRatio * PULSE_PER_REV); +} + +bool EYouEthercatProtocol::waitUntil(std::function condition, int timeout_ms) { + auto start = std::chrono::steady_clock::now(); + while (!condition()) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + if (std::chrono::steady_clock::now() - start > std::chrono::milliseconds(timeout_ms)) { + return false; + } + } + return true; +} + +void EYouEthercatProtocol::updateNodeState(uint8_t node_id) { + + // 更新状态字 + node_states_[node_id].status_word = master_->getStatusWord(node_id); + // 可以根据需要更新其他状态信息 +} \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.h b/cmvr-es/devices/motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.h new file mode 100644 index 00000000..2b14279c --- /dev/null +++ b/cmvr-es/devices/motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.h @@ -0,0 +1,125 @@ +// +// Created by cmvr on 2026/3/30. +// + +#pragma once +#include "cmvr/msgs/motor.pb.h" +#include "motor/motor_protocol_interface.h" +#include "../../../canbus/abstract_canbus.h" +#include "canbus/can_comm/can_receiver.h" +#include "canbus/can_comm/can_sender.h" +#include "canbus/can_comm/message_manager.h" +#include "cmvr/msgs/error_code.pb.h" +#include "cmvr/msgs/robot_detail.pb.h" + +// 替换原有的 eu_ethercat 头文件 +#include "protocol/ec_master.h" +#include "protocol/ec_xml_parser.h" +#include "protocol/ec_pdo_config.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace cmvr { + namespace device { + + // 节点状态结构体 + struct NodeState { + bool enabled = false; + bool initialized = false; + msgs::RunMode current_mode = msgs::RUN_MODE_UNSPECIFIED; + uint16_t status_word = 0; + int32_t position_offset = 0; + int32_t target_position = 0; + }; + + class EYouEthercatProtocol : public MotorProtocolInterface { + public: + explicit EYouEthercatProtocol(std::string ifName, const std::string& xmlPath = ""); + ~EYouEthercatProtocol() override; + + // 基础接口实现 + bool initNode(uint8_t node_id) override; + void setMode(uint8_t node_id, msgs::RunMode mode) override; + void setTarget(uint8_t node_id, double angle_rad, double vel) override; + void setTarget(uint8_t node_id, double vel) override; + void setQ(uint8_t node_id, double angle_rad) override; + void setLimitQ(uint8_t node_id, double ub, double lb) override; + void setLimitQd(uint8_t node_id, double qd) override; + void setLimitQdd(uint8_t node_id, double u_qdd, double l_qdd) override; + bool calibrateZeroQ(uint8_t node_id) override; + void brake(uint8_t node_id) override; + bool reachedTargetQ(uint8_t node_id) override; + + double getQ(uint8_t node_id) override; + double getQd(uint8_t node_id) override; + + void setQd(uint8_t node_id, double qd) override; + void setQdd(uint8_t node_id, double qdd) override; + + void torqueOff(uint8_t node_id) override; + msgs::RunMode getMode(uint8_t node_id) override; + + // 配置接口 + void configProfile(uint8_t node_id, uint32_t speed, uint32_t accel, uint32_t decel); + + // 扩展功能接口 + double getTorque(uint8_t node_id); + uint16_t getStatusWord(uint8_t node_id); + bool isEnabled(uint8_t node_id); + bool isFault(uint8_t node_id); + void faultReset(uint8_t node_id); + bool waitForTargetReached(uint8_t node_id, int timeout_ms); + + // 数据获取接口 + std::unique_ptr GetRobotDetail(); + + int getSlaveCount() const { return slave_count_; } + + private: + // 常量定义 + static constexpr double GearRatio = 101.0; + static constexpr double RADTODEG = 180.0 / M_PI; + static constexpr double PULSE_PER_REV = 65536.0; + + // 辅助函数 + double posToAngle(int32_t pos) const; + int32_t angleToPos(double angle) const; + double velToSpeed(double vel) const; + double speedToVel(int32_t speed) const; + + bool waitUntil(std::function condition, int timeout_ms); + void updateNodeState(uint8_t node_id); + bool performEnableSequence(uint8_t node_id); + + int8_t runModeToEcMode(msgs::RunMode mode); + msgs::RunMode ecModeToRunMode(int8_t mode); + + // 成员变量 + std::unordered_map node_states_; + std::unordered_map last_Qd_; + std::unordered_map last_Qdd_; + + std::shared_ptr> can_sender_; + std::shared_ptr> message_manager_; + + std::string interface_name_; + std::string xml_config_path_; + bool initialized_; + int slave_count_; + + std::unique_ptr master_; + std::unique_ptr parser_; + EcSlaveConfig slave_config_; + bool use_fixed_pdo_; + + std::mutex master_mutex_; + }; + + } // namespace device +} // namespace cmvr \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_master.cpp b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_master.cpp new file mode 100644 index 00000000..afac1e5c --- /dev/null +++ b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_master.cpp @@ -0,0 +1,1143 @@ +// ec_master.cpp +#include "ec_master.h" +#include + +static float pgain = 0.01f; +static float igain = 0.00002f; +static int64_t syncoffset = 500000; +static int64_t timeerror = 0; + +static void add_time_ns(ec_timet* ts, int64_t addtime) { + ec_timet addts; + addts.tv_nsec = addtime % 1000000000; + addts.tv_sec = (addtime - addts.tv_nsec) / 1000000000; + osal_timespecadd(ts, &addts, ts); +} + +// DC 同步 PI 计算 +static void ec_sync(int64_t reftime, int64_t cycletime, int64_t *offsettime) { + static int64_t integral = 0; + int64_t delta; + delta = (reftime - syncoffset) % cycletime; + if (delta > (cycletime / 2)) { + delta = delta - cycletime; + } + timeerror = -delta; + integral += timeerror; + *offsettime = (int64_t)((timeerror * pgain) + (integral * igain)); +} + +// ==================== 构造和析构 ==================== + +EcMaster::EcMaster() { + memset(&ctx, 0, sizeof(ctx)); + memset(IOmap, 0, sizeof(IOmap)); + + slaveCount = 0; + expectedWKC = 0; + currentgroup = 0; + inOp = false; + isRunning = false; + mappingdone = 0; + dorun = 0; + cyclicWkcErrorCount = 0; + cycleTimeNs = 1000000; + cyclicThread = nullptr; + checkThread = nullptr; + anyPdoDirty = false; +} + +EcMaster::~EcMaster() { + shutdown(); + // shadowBuffers 的析构函数会自动清理内存 +} + +// ==================== 初始化和状态控制 ==================== + +int EcMaster::init(const char* ifName, int cycleTimeUs) { + cycleTimeNs = cycleTimeUs * 1000; + + if (!ecx_init(&ctx, ifName)) { + printf("Failed to init EtherCAT on %s\n", ifName); + return -EC_FAILED_INIT; + } + + ecx_config_init(&ctx); + slaveCount = ctx.slavecount; + + if (slaveCount <= 0) { + printf("No slave found\n"); + return -EC_FAILED_NOSLAVE; + } + + printf("%d slaves found\n", slaveCount); + + isRunning = true; + mappingdone = 0; + dorun = 0; + inOp = false; + cyclicWkcErrorCount = 0; + anyPdoDirty = false; + + osal_thread_create_rt(&cyclicThread, 128000, reinterpret_cast(cyclicThreadFunc), this); + + return slaveCount; +} + +int EcMaster::setState(uint16_t slaveId, uint8_t targetState) { + if (slaveId > (uint16_t)ctx.slavecount) return -1; + + int oldManual = ctx.manualstatechange; + ctx.manualstatechange = 1; + + ctx.slavelist[slaveId].state = targetState; + ecx_writestate(&ctx, slaveId); + + int ret = ecx_statecheck(&ctx, slaveId, targetState, EC_TIMEOUTSTATE); + ctx.manualstatechange = oldManual; + + if (ret != targetState) { + printf("Slave %d failed to enter state %d (current: %d)\n", slaveId, targetState, ret); + return -1; + } + + return 0; +} + +int EcMaster::gotoOperational() { + while (!mappingdone) { + osal_usleep(100); + } + + dorun = 1; + + printf("Waiting for PDO exchange to stabilize...\n"); + osal_usleep(500000); + + // ========== 逐个处理每个从站 ========== + int allOperational = 1; + for (int i = 1; i <= ctx.slavecount; i++) { + printf("\n--- Processing slave %d ---\n", i); + + // 1. 检查并清除错误状态 + if (ctx.slavelist[i].state == (EC_STATE_SAFE_OP + EC_STATE_ERROR)) { + printf(" Clearing error on slave %d\n", i); + ctx.slavelist[i].state = (EC_STATE_SAFE_OP + EC_STATE_ACK); + ecx_writestate(&ctx, i); + osal_usleep(100000); + + // 刷新状态 + ecx_readstate(&ctx); + printf(" Slave %d state after error clear: 0x%02X\n", i, ctx.slavelist[i].state); + } + + // 2. 请求进入 OP 状态 + printf(" Requesting OPERATIONAL state for slave %d...\n", i); + ctx.slavelist[i].state = EC_STATE_OPERATIONAL; + ecx_writestate(&ctx, i); + + // 3. 等待该从站进入 OP 状态 + printf(" Waiting for slave %d to enter OPERATIONAL...\n", i); + + int retry = 0; + const int maxRetry = 200; // 最多等待2秒 (200 * 10ms) + int currentState = 0; + + while (retry < maxRetry) { + osal_usleep(10000); // 10ms + ecx_readstate(&ctx); + currentState = ctx.slavelist[i].state; + + if (currentState == EC_STATE_OPERATIONAL) { + printf(" ✓ Slave %d entered OPERATIONAL after %d ms\n", i, retry * 10); + break; + } + + // 如果状态异常,提前退出 + if (currentState == EC_STATE_INIT || currentState == EC_STATE_PRE_OP) { + printf(" ✗ Slave %d regressed to state 0x%02X\n", i, currentState); + break; + } + + retry++; + } + + // 4. 检查结果 + if (currentState == EC_STATE_OPERATIONAL) { + printf(" ✓ Slave %d is OPERATIONAL\n", i); + } else { + printf(" ✗ Slave %d failed to enter OPERATIONAL, state: 0x%02X\n", i, currentState); + + // 读取错误码 + uint16_t alStatusCode = 0; + int psize = 2; + ecx_SDOread(&ctx, i, 0x0134, 0, FALSE, &psize, &alStatusCode, EC_TIMEOUTRXM); + printf(" AL Status Code: 0x%04X\n", alStatusCode); + + allOperational = 0; + } + } + + // 5. 检查是否所有从站都进入了 OP + if (!allOperational) { + printf("\n✗ Failed to enter OPERATIONAL\n"); + return -1; + } + + printf("\n✓ All slaves are OPERATIONAL\n"); + + // 6. 启动 check 线程 + printf("Starting check thread for runtime monitoring...\n"); + osal_thread_create(&checkThread, 128000, reinterpret_cast(checkThreadFunc), this); + + osal_usleep(200000); + inOp = true; + printf("EtherCAT OPERATIONAL (all slaves)\n"); + return 0; +} + +void EcMaster::shutdown() { + printf("Shutting down...\n"); + + if (inOp) { + inOp = false; + dorun = 0; + osal_usleep(100000); + } + + for (int i = 1; i <= slaveCount; i++) { + printf(" Resetting slave %d to INIT...\n", i); + ctx.slavelist[i].state = EC_STATE_INIT; + ecx_writestate(&ctx, i); + osal_usleep(50000); + ecx_statecheck(&ctx, i, EC_STATE_INIT, 500000); + } + + isRunning = false; + osal_usleep(100000); + + shadowBuffers.clear(); + + printf("Shutdown complete\n"); +} + +// ==================== 影子缓冲区管理 ==================== + +EcMaster::SlaveShadowBuffer* EcMaster::getShadowBuffer(uint16_t slaveId) { + auto it = shadowBuffers.find(slaveId); + if (it != shadowBuffers.end()) { + return &it->second; + } + return nullptr; +} + +void EcMaster::syncSlaveOutputs(uint16_t slaveId) { + auto* shadow = getShadowBuffer(slaveId); + if (!shadow || !shadow->dirty || !shadow->data) return; + + auto outIt = outputs.find(slaveId); + auto sizeIt = outputSizes.find(slaveId); + if (outIt == outputs.end() || outIt->second == nullptr) return; + if (sizeIt == outputSizes.end()) return; + + size_t copySize = std::min(shadow->size, (size_t)sizeIt->second); + memcpy(outIt->second, shadow->data, copySize); + shadow->dirty = false; +} + +void EcMaster::syncAllOutputs() { + if (!anyPdoDirty.load()) return; + + for (auto& pair : shadowBuffers) { + uint16_t slaveId = pair.first; + SlaveShadowBuffer& shadow = pair.second; + + if (!shadow.dirty || !shadow.data) continue; + + auto outIt = outputs.find(slaveId); + auto sizeIt = outputSizes.find(slaveId); + if (outIt == outputs.end() || outIt->second == nullptr) continue; + if (sizeIt == outputSizes.end()) continue; + + size_t copySize = std::min(shadow.size, (size_t)sizeIt->second); + memcpy(outIt->second, shadow.data, copySize); + shadow.dirty = false; + } + + anyPdoDirty = false; +} + +// ==================== 线程函数 ==================== + +OSAL_THREAD_FUNC_RT EcMaster::cyclicThreadFunc(void* arg) { + static_cast(arg)->runCyclic(); +} + +OSAL_THREAD_FUNC EcMaster::checkThreadFunc(void* arg) { + static_cast(arg)->runCheck(); +} + +void EcMaster::runCyclic() { + ec_timet ts; + int ht; + static int64_t toff = 0; + int cycleCount = 0; + int printInterval = 500; + + printf("[Cyclic Thread] Thread started, waiting for mappingdone...\n"); + + while (!mappingdone && isRunning) { + osal_usleep(100); + } + + if (!isRunning) { + printf("[Cyclic Thread] Exiting - master not running\n"); + return; + } + + printf("[Cyclic Thread] Mapping done, initializing timing...\n"); + + osal_get_monotonic_time(&ts); + ht = (ts.tv_nsec / 1000000) + 1; + ts.tv_nsec = ht * 1000000; + + printf("[Cyclic Thread] Entering main cyclic loop (cycleTime=%lld ns)\n", + (long long)cycleTimeNs); + + int receiveSuccessCount = 0; + int receiveFailCount = 0; + int lastPrintCycle = 0; + + ecx_send_processdata(&ctx); + + while (isRunning) { + add_time_ns(&ts, cycleTimeNs + toff); + osal_monotonic_sleep(&ts); + + cycleCount++; + + if (dorun) { + // ========== 同步所有从站的影子缓冲区到 outputs ========== + if (anyPdoDirty.load()) { + for (auto& pair : shadowBuffers) { + uint16_t slaveId = pair.first; + SlaveShadowBuffer& shadow = pair.second; + + if (!shadow.dirty || !shadow.data) continue; + + auto outIt = outputs.find(slaveId); + auto sizeIt = outputSizes.find(slaveId); + if (outIt == outputs.end() || outIt->second == nullptr) continue; + if (sizeIt == outputSizes.end()) continue; + + size_t copySize = std::min(shadow.size, (size_t)sizeIt->second); + memcpy(outIt->second, shadow.data, copySize); + shadow.dirty = false; + } + anyPdoDirty = false; + } + + ecx_send_processdata(&ctx); + + int wkc = ecx_receive_processdata(&ctx, EC_TIMEOUTRET); + + if (wkc != expectedWKC) { + cyclicWkcErrorCount++; + receiveFailCount++; + if (cyclicWkcErrorCount <= 5 || cyclicWkcErrorCount % 100 == 0) { + printf("[Cyclic] WARNING: WKC mismatch! Expected=%d, Got=%d, ErrorCount=%d\n", + expectedWKC, wkc, cyclicWkcErrorCount.load()); + } + } else { + receiveSuccessCount++; + } + + if (ctx.slavelist[0].hasdc && (wkc > 0)) { + ec_sync(ctx.DCtime, cycleTimeNs, &toff); + } + + ecx_mbxhandler(&ctx, 0, 4); + + // if (cycleCount - lastPrintCycle >= printInterval) { + // lastPrintCycle = cycleCount; + // + // printf("\n========== PDO Data Exchange Status ==========\n"); + // printf("Cycle: %d | Success: %d | Fail: %d | WKC Error: %d\n", + // cycleCount, receiveSuccessCount, receiveFailCount, cyclicWkcErrorCount.load()); + // printf("DC time: %lld | toff: %lld\n", (long long)ctx.DCtime, (long long)toff); + // + // for (int slaveIdx = 1; slaveIdx <= slaveCount; slaveIdx++) { + // ec_slavet* slave = &ctx.slavelist[slaveIdx]; + // + // printf("\n[Slave %d] State: 0x%02X | AL Status: 0x%04X\n", + // slaveIdx, slave->state, slave->ALstatuscode); + // + // if (slave->inputs && slave->Ibytes > 0) { + // printf(" Inputs (%d bytes): ", slave->Ibytes); + // for (int i = 0; i < slave->Ibytes && i < 16; i++) { + // printf("%02X ", ((uint8_t*)slave->inputs)[i]); + // } + // if (slave->Ibytes > 16) printf("..."); + // printf("\n"); + // + // if (slave->Ibytes >= 2) { + // uint16_t statusWord = *(uint16_t*)slave->inputs; + // printf(" -> Status Word: 0x%04X", statusWord); + // if (statusWord == 0xFFFF) { + // printf(" (INVALID - All ones)"); + // } else if (statusWord == 0) { + // printf(" (INVALID - All zeros)"); + // } else { + // printf(" ["); + // if (statusWord & 0x0001) printf("ReadyToSwitchOn "); + // if (statusWord & 0x0002) printf("SwitchedOn "); + // if (statusWord & 0x0004) printf("OperationEnabled "); + // if (statusWord & 0x0008) printf("Fault "); + // if (statusWord & 0x0010) printf("VoltageEnabled "); + // if (statusWord & 0x0020) printf("QuickStop "); + // if (statusWord & 0x0040) printf("SwitchOnDisabled "); + // if (statusWord & 0x0080) printf("Warning "); + // printf("]"); + // } + // printf("\n"); + // + // if (slave->Ibytes >= 6) { + // int32_t actualPosition = *(int32_t*)(slave->inputs + 2); + // printf(" -> Actual Position: %d\n", actualPosition); + // } + // } + // } + // + // if (slave->outputs && slave->Obytes > 0) { + // printf(" Outputs (%d bytes): ", slave->Obytes); + // for (int i = 0; i < slave->Obytes && i < 16; i++) { + // printf("%02X ", ((uint8_t*)slave->outputs)[i]); + // } + // if (slave->Obytes > 16) printf("..."); + // printf("\n"); + // + // if (slave->Obytes >= 2) { + // uint16_t controlWord = *(uint16_t*)slave->outputs; + // printf(" <- Control Word: 0x%04X\n", controlWord); + // } + // } + // } + // + // printf("================================================\n\n"); + // + // receiveSuccessCount = 0; + // receiveFailCount = 0; + // } + + } else { + if (cycleCount % 1000 == 0) { + printf("[Cyclic] Waiting for dorun=1, cycle=%d\n", cycleCount); + } + } + } + + printf("[Cyclic Thread] Thread exiting\n"); +} + +void EcMaster::runCheck() { + while (isRunning) { + if (dorun && (cyclicWkcErrorCount > 2 || ctx.grouplist[currentgroup].docheckstate)) { + ctx.grouplist[currentgroup].docheckstate = FALSE; + ecx_readstate(&ctx); + + for (int slaveix = 1; slaveix <= ctx.slavecount; slaveix++) { + ec_slavet *slave = &ctx.slavelist[slaveix]; + + if ((slave->group == currentgroup) && (slave->state != EC_STATE_OPERATIONAL)) { + ctx.grouplist[currentgroup].docheckstate = TRUE; + + if (slave->state == (EC_STATE_SAFE_OP + EC_STATE_ERROR)) { + printf("ERROR : slave %d is in SAFE_OP + ERROR, attempting ack.\n", slaveix); + slave->state = (EC_STATE_SAFE_OP + EC_STATE_ACK); + ecx_writestate(&ctx, slaveix); + } + else if (slave->state == EC_STATE_SAFE_OP) { + printf("WARNING : slave %d is in SAFE_OP, change to OPERATIONAL.\n", slaveix); + slave->state = EC_STATE_OPERATIONAL; + if (slave->mbxhandlerstate == ECT_MBXH_LOST) + slave->mbxhandlerstate = ECT_MBXH_CYCLIC; + ecx_writestate(&ctx, slaveix); + } + else if (slave->state > EC_STATE_NONE) { + if (ecx_reconfig_slave(&ctx, slaveix, EC_TIMEOUTMON) >= EC_STATE_PRE_OP) { + slave->islost = FALSE; + printf("MESSAGE : slave %d reconfigured\n", slaveix); + } + } + else if (!slave->islost) { + ecx_statecheck(&ctx, slaveix, EC_STATE_OPERATIONAL, EC_TIMEOUTRET); + if (slave->state == EC_STATE_NONE) { + slave->islost = TRUE; + slave->mbxhandlerstate = ECT_MBXH_LOST; + if (slave->Ibytes) { + memset(slave->inputs, 0x00, slave->Ibytes); + } + printf("ERROR : slave %d lost\n", slaveix); + } + } + } + + if (slave->islost) { + if (slave->state <= EC_STATE_INIT) { + if (ecx_recover_slave(&ctx, slaveix, EC_TIMEOUTMON)) { + slave->islost = FALSE; + printf("MESSAGE : slave %d recovered\n", slaveix); + } + } + else { + slave->islost = FALSE; + printf("MESSAGE : slave %d found\n", slaveix); + } + } + } + + if (!ctx.grouplist[currentgroup].docheckstate) { + printf("OK : all slaves resumed OPERATIONAL.\n"); + } + } + + osal_usleep(10000); + } +} + +// ==================== PDO 配置 ==================== + +int EcMaster::configurePdo(uint16_t slaveId, const EcPdo* rxPdo, const EcPdo* txPdo) { + uint8_t zero = 0; + int wkc = 0; + + // ========== 步骤1: 进入 PRE-OP ========== + printf("Step 1: Entering PRE-OP...\n"); + if (setState(slaveId, EC_STATE_PRE_OP) != 0) { + printf("Failed to enter PRE-OP\n"); + return -1; + } + printf("Slave %d in PRE-OP\n", slaveId); + osal_usleep(50000); + + // ========== 步骤2: 禁用全局 PDO 分配 ========== + printf("Step 2: Disabling global PDO assignment...\n"); + wkc = ecx_SDOwrite(&ctx, slaveId, 0x1C12, 0x00, FALSE, 1, &zero, EC_TIMEOUTRXM); + printf("ecx_SDOwrite 0x1C12, wkc = %d\n", wkc); + + wkc = ecx_SDOwrite(&ctx, slaveId, 0x1C13, 0x00, FALSE, 1, &zero, EC_TIMEOUTRXM); + printf("ecx_SDOwrite 0x1C13, wkc = %d\n", wkc); + + // ========== 计算输出大小(用于分配影子缓冲区) ========== + int outputSize = 0; + if (rxPdo) { + for (const auto& entry : rxPdo->entries) { + outputSize += (entry.bitlen + 7) / 8; + } + printf("Calculated output size: %d bytes\n", outputSize); + } + + // ========== 步骤3: 配置 RxPDO ========== + if (rxPdo && rxPdo->mapCount > 0) { + printf("Step 3: Configuring RxPDO 0x%04X\n", rxPdo->index); + + wkc = ecx_SDOwrite(&ctx, slaveId, rxPdo->index, 0x00, FALSE, 1, &zero, EC_TIMEOUTRXM); + if (wkc <= 0) { + printf("Failed to disable RxPDO 0x%04X\n", rxPdo->index); + return -1; + } + + uint8_t subidx = 1; + for (const auto& entry : rxPdo->entries) { + uint32_t mapValue = entry.makeMapValue(); + wkc = ecx_SDOwrite(&ctx, slaveId, rxPdo->index, subidx++, FALSE, 4, &mapValue, EC_TIMEOUTRXM); + if (wkc <= 0) { + printf("Failed to write RxPDO entry 0x%04X:%02X\n", entry.index, entry.subindex); + return -1; + } + printf(" Entry: 0x%04X:%02X (%d bits)\n", entry.index, entry.subindex, entry.bitlen); + } + + uint8_t mapCount = rxPdo->mapCount; + wkc = ecx_SDOwrite(&ctx, slaveId, rxPdo->index, 0x00, FALSE, 1, &mapCount, EC_TIMEOUTRXM); + if (wkc <= 0) { + printf("Failed to enable RxPDO 0x%04X\n", rxPdo->index); + return -1; + } + printf("RxPDO 0x%04X configured successfully\n", rxPdo->index); + } + + // ========== 步骤4: 配置 TxPDO ========== + if (txPdo && txPdo->mapCount > 0) { + printf("Step 4: Configuring TxPDO 0x%04X\n", txPdo->index); + + wkc = ecx_SDOwrite(&ctx, slaveId, txPdo->index, 0x00, FALSE, 1, &zero, EC_TIMEOUTRXM); + if (wkc <= 0) { + printf("Failed to disable TxPDO 0x%04X\n", txPdo->index); + return -1; + } + + uint8_t subidx = 1; + for (const auto& entry : txPdo->entries) { + uint32_t mapValue = entry.makeMapValue(); + wkc = ecx_SDOwrite(&ctx, slaveId, txPdo->index, subidx++, FALSE, 4, &mapValue, EC_TIMEOUTRXM); + if (wkc <= 0) { + printf("Failed to write TxPDO entry 0x%04X:%02X\n", entry.index, entry.subindex); + return -1; + } + printf(" Entry: 0x%04X:%02X (%d bits)\n", entry.index, entry.subindex, entry.bitlen); + } + + uint8_t mapCount = txPdo->mapCount; + wkc = ecx_SDOwrite(&ctx, slaveId, txPdo->index, 0x00, FALSE, 1, &mapCount, EC_TIMEOUTRXM); + if (wkc <= 0) { + printf("Failed to enable TxPDO 0x%04X\n", txPdo->index); + return -1; + } + printf("TxPDO 0x%04X configured successfully\n", txPdo->index); + } + + // ========== 步骤5: 应用 PDO 分配 ========== + printf("Step 5: Applying PDO assignment...\n"); + + if (rxPdo && rxPdo->mapCount > 0) { + uint8_t rpdo_map_count = 1; + wkc = ecx_SDOwrite(&ctx, slaveId, 0x1C12, 0x01, FALSE, 2, &rxPdo->index, EC_TIMEOUTRXM); + if (wkc <= 0) { + printf("Failed to write RxPDO index to 0x1C12:01\n"); + return -1; + } + + wkc = ecx_SDOwrite(&ctx, slaveId, 0x1C12, 0x00, FALSE, 1, &rpdo_map_count, EC_TIMEOUTRXM); + if (wkc <= 0) { + printf("Failed to set RxPDO count\n"); + return -1; + } + printf("RxPDO assigned to 0x1C12\n"); + } + + if (txPdo && txPdo->mapCount > 0) { + uint8_t tpdo_map_count = 1; + wkc = ecx_SDOwrite(&ctx, slaveId, 0x1C13, 0x01, FALSE, 2, &txPdo->index, EC_TIMEOUTRXM); + if (wkc <= 0) { + printf("Failed to write TxPDO index to 0x1C13:01\n"); + return -1; + } + + wkc = ecx_SDOwrite(&ctx, slaveId, 0x1C13, 0x00, FALSE, 1, &tpdo_map_count, EC_TIMEOUTRXM); + if (wkc <= 0) { + printf("Failed to set TxPDO count\n"); + return -1; + } + printf("TxPDO assigned to 0x1C13\n"); + } + + // ========== 步骤6: 为这个从站分配影子缓冲区 ========== + if (outputSize > 0) { + // 移除旧的影子缓冲区(如果存在) + auto it = shadowBuffers.find(slaveId); + if (it != shadowBuffers.end()) { + shadowBuffers.erase(it); + } + // 创建新的影子缓冲区 + shadowBuffers.emplace(std::piecewise_construct, + std::forward_as_tuple(slaveId), + std::forward_as_tuple(outputSize)); + printf("Shadow buffer allocated for slave %d: %d bytes\n", slaveId, outputSize); + } + + // ========== 步骤7: 设置 PDO 偏移 ========== + setupPdoOffsets(slaveId, rxPdo, txPdo); + + printf("PDO configuration for slave %d completed\n", slaveId); + return 0; +} + +// ========== setupMappingAndDc - 只需要做映射和DC配置 ========== +int EcMaster::setupMappingAndDc() { + printf("\n========== Step 6: Finalizing mapping and DC configuration ==========\n"); + + // 配置映射 + ctx.manualstatechange = 0; + ecx_config_map_group(&ctx, IOmap, 0); + + // 设置 mappingdone 标志 + mappingdone = 1; + + // 验证并保存所有从站的 PDO 数据指针和偏移 + for (int i = 1; i <= ctx.slavecount; i++) { + inputs[i] = ctx.slavelist[i].inputs; + outputs[i] = ctx.slavelist[i].outputs; + inputSizes[i] = ctx.slavelist[i].Ibytes; + outputSizes[i] = ctx.slavelist[i].Obytes; + + if (outputs[i]) { + outputOffsets[i] = (uint8_t*)outputs[i] - IOmap; + printf("Slave %d: Out offset=%d, size=%d bytes\n", + i, outputOffsets[i], outputSizes[i]); + } + printf("Slave %d: In=%d bytes, Out=%d bytes\n", i, inputSizes[i], outputSizes[i]); + + printf(" SM2 (Outputs): StartAddr=0x%04X, Length=%d\n", + ctx.slavelist[i].SM[2].StartAddr, ctx.slavelist[i].SM[2].SMlength); + printf(" SM3 (Inputs): StartAddr=0x%04X, Length=%d\n", + ctx.slavelist[i].SM[3].StartAddr, ctx.slavelist[i].SM[3].SMlength); + } + + expectedWKC = (ctx.grouplist[0].outputsWKC * 2) + ctx.grouplist[0].inputsWKC; + printf("Expected WKC: %d\n", expectedWKC); + + // 配置分布式时钟 + ecx_configdc(&ctx); + + // 添加所有 CoE 从站到周期邮箱处理 + for (int i = 1; i <= ctx.slavecount; i++) { + if (ctx.slavelist[i].CoEdetails > 0) { + ecx_slavembxcyclic(&ctx, i); + printf("Slave %d added to cyclic mailbox handler\n", i); + } + } + + // 验证影子缓冲区大小是否匹配实际输出大小 + for (int i = 1; i <= ctx.slavecount; i++) { + auto it = shadowBuffers.find(i); + if (it != shadowBuffers.end()) { + size_t shadowSize = it->second.size; + int actualSize = outputSizes[i]; + if (shadowSize != (size_t)actualSize) { + printf("WARNING: Slave %d shadow buffer size (%zu) != actual output size (%d)\n", + i, shadowSize, actualSize); + // 重新分配匹配的大小 + it->second = SlaveShadowBuffer(actualSize); + printf(" Reallocated shadow buffer to %d bytes\n", actualSize); + } + } else if (outputSizes[i] > 0) { + // 如果没有影子缓冲区但有输出数据,创建一个 + shadowBuffers.emplace(std::piecewise_construct, + std::forward_as_tuple(i), + std::forward_as_tuple(outputSizes[i])); + printf("Created shadow buffer for slave %d: %d bytes\n", i, outputSizes[i]); + } + } + + printf("Mapping and DC configuration completed\n"); + return 0; +} + +int EcMaster::setupPdoOffsets(uint16_t slaveId, const EcPdo* rxPdo, const EcPdo* txPdo) { + EcPdoOffsets offsets; + memset(&offsets, 0xFF, sizeof(offsets)); + + // ========== 解析 RxPDO (Outputs) ========== + if (rxPdo) { + uint16_t offset = 0; + printf("RxPDO 0x%04X mapping (%zu entries):\n", rxPdo->index, rxPdo->entries.size()); + + for (const auto& entry : rxPdo->entries) { + uint8_t bytes = (entry.bitlen + 7) / 8; + printf(" Entry 0x%04X:%02X -> offset %d (%d bytes)\n", + entry.index, entry.subindex, offset, bytes); + + switch (entry.index) { + case 0x6040: // Control Word + offsets.controlWord = offset; + printf(" *** Control Word at offset %d ***\n", offset); + break; + case 0x607A: // Target Position + offsets.targetPosition = offset; + break; + case 0x60FF: // Target Velocity + offsets.targetVelocity = offset; + break; + case 0x6071: // Target Torque + offsets.targetTorque = offset; + break; + case 0x6083: // Profile Acceleration + offsets.profileAcceleration = offset; + printf(" *** Profile Acceleration at offset %d ***\n", offset); + break; + case 0x6084: // Profile Deceleration + offsets.profileDeceleration = offset; + printf(" *** Profile Deceleration at offset %d ***\n", offset); + break; + case 0x6081: // Profile Velocity + offsets.profileVelocity = offset; + printf(" *** Profile Velocity at offset %d ***\n", offset); + break; + case 0x6087: // Torque Slope + offsets.torqueSlope = offset; + break; + case 0x6060: // Mode of Operation + offsets.modeOfOperation = offset; + break; + default: + printf(" *** Unknown entry 0x%04X:%02X ***\n", entry.index, entry.subindex); + break; + } + offset += bytes; + } + } + + // ========== 解析 TxPDO (Inputs) ========== + if (txPdo) { + uint16_t offset = 0; + printf("\nTxPDO 0x%04X mapping (%zu entries):\n", txPdo->index, txPdo->entries.size()); + + for (const auto& entry : txPdo->entries) { + uint8_t bytes = (entry.bitlen + 7) / 8; + printf(" Entry 0x%04X:%02X -> offset %d (%d bytes)\n", + entry.index, entry.subindex, offset, bytes); + + switch (entry.index) { + case 0x6041: // Status Word + offsets.statusWord = offset; + printf(" *** Status Word at offset %d ***\n", offset); + break; + case 0x6064: // Actual Position + offsets.actualPosition = offset; + break; + case 0x606C: // Actual Velocity + offsets.actualVelocity = offset; + break; + case 0x6077: // Actual Torque + offsets.actualTorque = offset; + break; + case 0x6061: // Mode of Operation Display + offsets.modeOfOperationDisplay = offset; + break; + case 0x603F: // Error Code + offsets.errorCode = offset; + break; + case 0x6078: // Actual Current + offsets.actualCurrent = offset; + printf(" *** Actual Current at offset %d ***\n", offset); + break; + default: + printf(" *** Unknown entry 0x%04X:%02X ***\n", entry.index, entry.subindex); + break; + } + offset += bytes; + } + } + + pdoOffsets[slaveId] = offsets; + + // ========== 打印摘要 ========== + printf("\n=== PDO Offset Summary ===\n"); + printf("RxPDO (Outputs):\n"); + printf(" Control Word: %d %s\n", offsets.controlWord, + offsets.controlWord == 0xFFFF ? "(INVALID)" : ""); + printf(" Target Position: %d %s\n", offsets.targetPosition, + offsets.targetPosition == 0xFFFF ? "(INVALID)" : ""); + printf(" Target Velocity: %d %s\n", offsets.targetVelocity, + offsets.targetVelocity == 0xFFFF ? "(INVALID)" : ""); + printf(" Target Torque: %d %s\n", offsets.targetTorque, + offsets.targetTorque == 0xFFFF ? "(INVALID)" : ""); + printf(" Profile Velocity: %d %s\n", offsets.profileVelocity, + offsets.profileVelocity == 0xFFFF ? "(NOT IN PDO)" : ""); + printf(" Profile Acc: %d %s\n", offsets.profileAcceleration, + offsets.profileAcceleration == 0xFFFF ? "(NOT IN PDO)" : ""); + printf(" Profile Dec: %d %s\n", offsets.profileDeceleration, + offsets.profileDeceleration == 0xFFFF ? "(NOT IN PDO)" : ""); + printf(" Mode of Operation: %d %s\n", offsets.modeOfOperation, + offsets.modeOfOperation == 0xFFFF ? "(INVALID)" : ""); + + printf("\nTxPDO (Inputs):\n"); + printf(" Status Word: %d %s\n", offsets.statusWord, + offsets.statusWord == 0xFFFF ? "(INVALID)" : ""); + printf(" Actual Position: %d %s\n", offsets.actualPosition, + offsets.actualPosition == 0xFFFF ? "(INVALID)" : ""); + printf(" Actual Velocity: %d %s\n", offsets.actualVelocity, + offsets.actualVelocity == 0xFFFF ? "(INVALID)" : ""); + printf(" Actual Torque: %d %s\n", offsets.actualTorque, + offsets.actualTorque == 0xFFFF ? "(INVALID)" : ""); + printf(" Mode Display: %d %s\n", offsets.modeOfOperationDisplay, + offsets.modeOfOperationDisplay == 0xFFFF ? "(INVALID)" : ""); + printf(" Error Code: %d %s\n", offsets.errorCode, + offsets.errorCode == 0xFFFF ? "(NOT IN PDO)" : ""); + printf(" Actual Current: %d %s\n", offsets.actualCurrent, + offsets.actualCurrent == 0xFFFF ? "(NOT IN PDO)" : ""); + printf("=================================\n"); + + if (offsets.controlWord == 0xFFFF || offsets.statusWord == 0xFFFF) { + printf("ERROR: Critical PDO offsets not configured!\n"); + return -1; + } + + return 0; +} + +// ==================== PDO 访问接口 ==================== + +void EcMaster::setControlWord(uint16_t slaveId, uint16_t value) { + if (inOp) { + writePdo(slaveId, pdoOffsets[slaveId].controlWord, value); + } else { + writeSdo(slaveId, 0x6040, 0, &value, EcDataType::Uint16, 100); + } +} + +uint16_t EcMaster::getStatusWord(uint16_t slaveId) { + if (inOp) { + return readPdo(slaveId, pdoOffsets[slaveId].statusWord); + } + uint16_t value; + readSdo(slaveId, 0x6041, 0, &value, EcDataType::Uint16, 100); + return value; +} + +void EcMaster::setTargetPosition(uint16_t slaveId, int32_t value) { + if (inOp) { + writePdo(slaveId, pdoOffsets[slaveId].targetPosition, value); + } else { + writeSdo(slaveId, 0x607A, 0, &value, EcDataType::Int32, 100); + } +} + +int32_t EcMaster::getActualPosition(uint16_t slaveId) { + if (inOp) { + return readPdo(slaveId, pdoOffsets[slaveId].actualPosition); + } + int32_t value; + readSdo(slaveId, 0x6064, 0, &value, EcDataType::Int32, 100); + return value; +} + +void EcMaster::setTargetVelocity(uint16_t slaveId, int32_t value) { + if (inOp) { + writePdo(slaveId, pdoOffsets[slaveId].targetVelocity, value); + } else { + writeSdo(slaveId, 0x60FF, 0, &value, EcDataType::Int32, 100); + } +} + +int32_t EcMaster::getActualVelocity(uint16_t slaveId) { + if (inOp) { + return readPdo(slaveId, pdoOffsets[slaveId].actualVelocity); + } + int32_t value; + readSdo(slaveId, 0x606C, 0, &value, EcDataType::Int32, 100); + return value; +} + +void EcMaster::setTargetTorque(uint16_t slaveId, int16_t value) { + if (inOp) { + writePdo(slaveId, pdoOffsets[slaveId].targetTorque, value); + } else { + writeSdo(slaveId, 0x6071, 0, &value, EcDataType::Int16, 100); + } +} + +int16_t EcMaster::getActualTorque(uint16_t slaveId) { + if (inOp) { + return readPdo(slaveId, pdoOffsets[slaveId].actualTorque); + } + int16_t value; + readSdo(slaveId, 0x6077, 0, &value, EcDataType::Int16, 100); + return value; +} + +void EcMaster::setModeOfOperation(uint16_t slaveId, int8_t value) { + if (inOp) { + writePdo(slaveId, pdoOffsets[slaveId].modeOfOperation, value); + } else { + writeSdo(slaveId, 0x6060, 0, &value, EcDataType::Int8, 100); + } +} + +int8_t EcMaster::getModeOfOperationDisplay(uint16_t slaveId) { + if (inOp) { + return readPdo(slaveId, pdoOffsets[slaveId].modeOfOperationDisplay); + } + int8_t value; + readSdo(slaveId, 0x6061, 0, &value, EcDataType::Int8, 100); + return value; +} + +void EcMaster::setProfileVelocity(uint16_t slaveId, uint32_t value) { + if (inOp) { + writePdo(slaveId, pdoOffsets[slaveId].profileVelocity, value); + } +} + +void EcMaster::setProfileAcceleration(uint16_t slaveId, uint32_t value) { + if (inOp) { + writePdo(slaveId, pdoOffsets[slaveId].profileAcceleration, value); + } +} + +void EcMaster::setProfileDeceleration(uint16_t slaveId, uint32_t value) { + if (inOp) { + writePdo(slaveId, pdoOffsets[slaveId].profileDeceleration, value); + } +} + +int16_t EcMaster::getCurrentValue(uint16_t slaveId) { + if (inOp) { + return readPdo(slaveId, pdoOffsets[slaveId].actualCurrent); + } + int16_t value; + readSdo(slaveId, 0x6078, 0, &value, EcDataType::Int16, 100); + return value; +} + +uint16_t EcMaster::getErrorCode(uint16_t slaveId) { + if (inOp) { + auto it = pdoOffsets.find(slaveId); + if (it != pdoOffsets.end() && it->second.errorCode != 0xFFFF) { + return readPdo(slaveId, it->second.errorCode); + } + } + uint16_t value = 0; + readSdo(slaveId, 0x603F, 0, &value, EcDataType::Uint16, 100); + return value; +} + +// ==================== 电机控制高级接口 ==================== + +int EcMaster::enable(uint16_t slaveId) { + if (inOp) { + setControlWord(slaveId, 0x0006); + osal_usleep(10000); + setControlWord(slaveId, 0x0007); + osal_usleep(10000); + setControlWord(slaveId, 0x000F); + } else { + uint16_t val = 0x0006; + writeSdo(slaveId, 0x6040, 0, &val, EcDataType::Uint16, 100); + osal_usleep(10000); + val = 0x0007; + writeSdo(slaveId, 0x6040, 0, &val, EcDataType::Uint16, 100); + osal_usleep(10000); + val = 0x000F; + writeSdo(slaveId, 0x6040, 0, &val, EcDataType::Uint16, 100); + } + osal_usleep(10000); + return 0; +} + +int EcMaster::disable(uint16_t slaveId) { + if (inOp) { + setControlWord(slaveId, 0x0007); + osal_usleep(10000); + setControlWord(slaveId, 0x0000); + } else { + uint16_t val = 0x0007; + writeSdo(slaveId, 0x6040, 0, &val, EcDataType::Uint16, 100); + osal_usleep(10000); + val = 0x0000; + writeSdo(slaveId, 0x6040, 0, &val, EcDataType::Uint16, 100); + } + return 0; +} + +int EcMaster::faultReset(uint16_t slaveId) { + if (inOp) { + setControlWord(slaveId, 0x0080); + osal_usleep(10000); + setControlWord(slaveId, 0x0000); + } else { + uint16_t val = 0x0080; + writeSdo(slaveId, 0x6040, 0, &val, EcDataType::Uint16, 100); + osal_usleep(10000); + val = 0x0000; + writeSdo(slaveId, 0x6040, 0, &val, EcDataType::Uint16, 100); + } + return 0; +} + +// ==================== SDO 读写 ==================== + +int EcMaster::readSdo(uint16_t slaveId, uint16_t index, uint8_t subindex, void* value, EcDataType type, int timeoutMs) { + int size = 0; + switch (type) { + case EcDataType::Boolean: + case EcDataType::Int8: + case EcDataType::Uint8: + size = 1; break; + case EcDataType::Int16: + case EcDataType::Uint16: + size = 2; break; + case EcDataType::Int32: + case EcDataType::Uint32: + case EcDataType::Real32: + size = 4; break; + case EcDataType::Real64: + size = 8; break; + default: return -1; + } + int wkc = ecx_SDOread(&ctx, slaveId, index, subindex, FALSE, &size, value, timeoutMs); + return wkc > 0 ? 0 : -1; +} + +int EcMaster::writeSdo(uint16_t slaveId, uint16_t index, uint8_t subindex, void* value, EcDataType type, int timeoutMs) { + int size = 0; + switch (type) { + case EcDataType::Boolean: + case EcDataType::Int8: + case EcDataType::Uint8: + size = 1; break; + case EcDataType::Int16: + case EcDataType::Uint16: + size = 2; break; + case EcDataType::Int32: + case EcDataType::Uint32: + case EcDataType::Real32: + size = 4; break; + case EcDataType::Real64: + size = 8; break; + default: return -1; + } + int wkc = ecx_SDOwrite(&ctx, slaveId, index, subindex, FALSE, size, value, timeoutMs); + return wkc > 0 ? 0 : -1; +} + +// ==================== SM/FMMU 配置校验 ==================== + +void EcMaster::verifySmConfiguration(uint16_t slaveId) { + if (slaveId > (uint16_t)ctx.slavecount) { + printf("Invalid slave ID: %d\n", slaveId); + return; + } + + ec_slavet* slave = &ctx.slavelist[slaveId]; + + printf("\n========== SM Configuration for Slave %d ==========\n", slaveId); + + printf("PDO Mapping Sizes:\n"); + printf(" Outputs (Obytes): %d bytes\n", slave->Obytes); + printf(" Inputs (Ibytes): %d bytes\n", slave->Ibytes); + + printf("\nSync Manager Configuration:\n"); + const char* sm_names[] = {"MBoxOut", "MBoxIn", "Outputs", "Inputs"}; + for (int sm = 0; sm < 4; sm++) { + printf(" SM%d (%s): StartAddr=0x%04X, Length=%d bytes\n", + sm, sm_names[sm], + slave->SM[sm].StartAddr, + slave->SM[sm].SMlength); + } + + printf("\nFMMU Configuration:\n"); + printf(" Outputs mapped to IOmap at offset: %td\n", + (uint8_t*)slave->outputs - IOmap); + printf(" Inputs mapped to IOmap at offset: %td\n", + (uint8_t*)slave->inputs - IOmap); + + // 打印影子缓冲区信息 + auto it = shadowBuffers.find(slaveId); + if (it != shadowBuffers.end()) { + printf("\nShadow Buffer:\n"); + printf(" Size: %zu bytes\n", it->second.size); + printf(" Dirty: %s\n", it->second.dirty ? "true" : "false"); + } else { + printf("\nShadow Buffer: Not allocated\n"); + } + + printf("==================================================\n"); +} + +void EcMaster::verifyAllSlavesSmConfiguration() { + for (int i = 1; i <= ctx.slavecount; i++) { + verifySmConfiguration((uint16_t)i); + } +} \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_master.h b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_master.h new file mode 100644 index 00000000..e4b67ad0 --- /dev/null +++ b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_master.h @@ -0,0 +1,328 @@ +// ec_master.h +#ifndef EC_MASTER_H +#define EC_MASTER_H + +#include "ec_types.h" +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +#include "soem/soem.h" +#include "soem/osal.h" +#ifdef __cplusplus +} +#endif + +// 错误码定义 +#define EC_SUCCESS 0 +#define EC_FAILED_INIT 1 +#define EC_FAILED_NOSLAVE 2 + +#ifndef EC_TIMEOUTMON +#define EC_TIMEOUTMON 500 +#endif + +#ifndef EC_TIMEOUTRET +#define EC_TIMEOUTRET 2000 +#endif + +#ifndef EC_TIMEOUTSTATE +#define EC_TIMEOUTSTATE 2000 +#endif + +#ifndef EC_TIMEOUTRXM +#define EC_TIMEOUTRXM 2000 +#endif + +// EtherCAT 主站类 +class EcMaster { + +public: + // 操作模式枚举(符合 CiA 402 标准) + enum EcOperationMode : int8_t { + // 轮廓模式 (Profile Modes) + EcOperationMode_ProfilePosition = 1, // PP 模式 + EcOperationMode_ProfileVelocity = 3, // PV 模式 + EcOperationMode_ProfileTorque = 4, // PT 模式 + + // 插补模式 + EcOperationMode_InterpolatedPosition = 7, // IP 模式 + + // 循环同步模式 (Cyclic Sync Modes) + EcOperationMode_CyclicSyncPosition = 8, // CSP 模式 + EcOperationMode_CyclicSyncVelocity = 9, // CSV 模式 + EcOperationMode_CyclicSyncTorque = 10, // CST 模式 + }; + +public: + EcMaster(); + ~EcMaster(); + + EcMaster(const EcMaster&) = delete; + EcMaster& operator=(const EcMaster&) = delete; + + // 初始化 + int init(const char* ifName, int cycleTimeUs = 1000); + void close() { shutdown(); } + int getSlaveCount() const { return slaveCount; } + bool isOperational() const { return inOp; } + + // 状态控制 + int setState(uint16_t slaveId, uint8_t targetState); + int preOperational(uint16_t slaveId) { return setState(slaveId, EC_STATE_PRE_OP); } + int safeOperational(uint16_t slaveId) { return setState(slaveId, EC_STATE_SAFE_OP); } + int operational(uint16_t slaveId) { return setState(slaveId, EC_STATE_OPERATIONAL); } + int gotoOperational(); + void shutdown(); + + // PDO 配置 + int configurePdo(uint16_t slaveId, const EcPdo* rxPdo, const EcPdo* txPdo); + int setupMappingAndDc(); // 配置所有从站的映射和DC + int setupPdoOffsets(uint16_t slaveId, const EcPdo* rxPdo, const EcPdo* txPdo); + + // PDO 数据访问(高性能,零拷贝) + void setControlWord(uint16_t slaveId, uint16_t value); + uint16_t getStatusWord(uint16_t slaveId); + void setTargetPosition(uint16_t slaveId, int32_t value); + int32_t getActualPosition(uint16_t slaveId); + void setTargetVelocity(uint16_t slaveId, int32_t value); + int32_t getActualVelocity(uint16_t slaveId); + void setTargetTorque(uint16_t slaveId, int16_t value); + int16_t getActualTorque(uint16_t slaveId); + void setModeOfOperation(uint16_t slaveId, int8_t value); + int8_t getModeOfOperationDisplay(uint16_t slaveId); + + void setProfileVelocity(uint16_t slaveId, uint32_t value); + void setProfileAcceleration(uint16_t slaveId, uint32_t value); + void setProfileDeceleration(uint16_t slaveId, uint32_t value); + int16_t getCurrentValue(uint16_t slaveId); + uint16_t getErrorCode(uint16_t slaveId); + + // 电机控制高级接口 + int enable(uint16_t slaveId); + int disable(uint16_t slaveId); + int faultReset(uint16_t slaveId); + + // 周期操作 + int sendOutputs() { return ecx_send_processdata(&ctx); } + int receiveInputs() { return ecx_receive_processdata(&ctx, EC_TIMEOUTRET); } + + // 影子缓冲区同步接口 + void syncSlaveOutputs(uint16_t slaveId); // 同步指定从站的输出 + void syncAllOutputs(); // 同步所有从站的输出 + + // 原始数据访问 + uint8_t* getInputs(uint16_t slaveId) { + auto it = inputs.find(slaveId); + return (it != inputs.end()) ? it->second : nullptr; + } + uint8_t* getOutputs(uint16_t slaveId) { + auto it = outputs.find(slaveId); + return (it != outputs.end()) ? it->second : nullptr; + } + int getInputSize(uint16_t slaveId) { + auto it = inputSizes.find(slaveId); + return (it != inputSizes.end()) ? it->second : 0; + } + int getOutputSize(uint16_t slaveId) { + auto it = outputSizes.find(slaveId); + return (it != outputSizes.end()) ? it->second : 0; + } + + // SDO 读写(仅用于配置阶段) + int readSdo(uint16_t slaveId, uint16_t index, uint8_t subindex, void* value, EcDataType type, int timeoutMs = 100); + int writeSdo(uint16_t slaveId, uint16_t index, uint8_t subindex, void* value, EcDataType type, int timeoutMs = 100); + + // SM/FMMU 配置校验(调试用) + void verifySmConfiguration(uint16_t slaveId); + void verifyAllSlavesSmConfiguration(); + + // 模板便捷接口 + template + int readSdo(uint16_t slaveId, uint16_t index, uint8_t subindex, T* value, int timeoutMs = 100) { + EcDataType type; + if constexpr (std::is_same_v) type = EcDataType::Int8; + else if constexpr (std::is_same_v) type = EcDataType::Uint8; + else if constexpr (std::is_same_v) type = EcDataType::Int16; + else if constexpr (std::is_same_v) type = EcDataType::Uint16; + else if constexpr (std::is_same_v) type = EcDataType::Int32; + else if constexpr (std::is_same_v) type = EcDataType::Uint32; + else if constexpr (std::is_same_v) type = EcDataType::Real32; + else if constexpr (std::is_same_v) type = EcDataType::Real64; + else return -1; + return readSdo(slaveId, index, subindex, value, type, timeoutMs); + } + + template + int writeSdo(uint16_t slaveId, uint16_t index, uint8_t subindex, T* value, int timeoutMs = 100) { + EcDataType type; + if constexpr (std::is_same_v) type = EcDataType::Int8; + else if constexpr (std::is_same_v) type = EcDataType::Uint8; + else if constexpr (std::is_same_v) type = EcDataType::Int16; + else if constexpr (std::is_same_v) type = EcDataType::Uint16; + else if constexpr (std::is_same_v) type = EcDataType::Int32; + else if constexpr (std::is_same_v) type = EcDataType::Uint32; + else if constexpr (std::is_same_v) type = EcDataType::Real32; + else if constexpr (std::is_same_v) type = EcDataType::Real64; + else return -1; + return writeSdo(slaveId, index, subindex, value, type, timeoutMs); + } + +private: + // ========== 影子缓冲区结构(每个从站独立) ========== + struct SlaveShadowBuffer { + uint8_t* data = nullptr; // 影子缓冲区数据指针 + size_t size = 0; // 缓冲区大小 + bool dirty = false; // 是否有待同步的数据 + + SlaveShadowBuffer() = default; + + explicit SlaveShadowBuffer(size_t sz) : size(sz) { + if (sz > 0) { + data = new uint8_t[sz]; + memset(data, 0, sz); + } + } + + ~SlaveShadowBuffer() { + delete[] data; + data = nullptr; + } + + // 禁止拷贝 + SlaveShadowBuffer(const SlaveShadowBuffer&) = delete; + SlaveShadowBuffer& operator=(const SlaveShadowBuffer&) = delete; + + // 允许移动 + SlaveShadowBuffer(SlaveShadowBuffer&& other) noexcept + : data(other.data), size(other.size), dirty(other.dirty) { + other.data = nullptr; + other.size = 0; + other.dirty = false; + } + + SlaveShadowBuffer& operator=(SlaveShadowBuffer&& other) noexcept { + if (this != &other) { + delete[] data; + data = other.data; + size = other.size; + dirty = other.dirty; + other.data = nullptr; + other.size = 0; + other.dirty = false; + } + return *this; + } + + // 重新分配缓冲区 + void reallocate(size_t newSize) { + if (data) { + delete[] data; + data = nullptr; + } + size = newSize; + if (newSize > 0) { + data = new uint8_t[newSize]; + memset(data, 0, newSize); + } + dirty = false; + } + }; + + // SOEM 上下文 + ecx_contextt ctx; + uint8_t IOmap[4096]; + + // 每个从站的影子缓冲区映射 + std::map shadowBuffers; + std::atomic anyPdoDirty{false}; // 是否有任何影子缓冲区有更新 + + int slaveCount = 0; + int expectedWKC = 0; + int currentgroup = 0; + + // 状态标志 + std::atomic inOp{false}; + std::atomic isRunning{false}; + std::atomic mappingdone{0}; + std::atomic dorun{0}; + std::atomic cyclicWkcErrorCount{0}; + + int64_t cycleTimeNs = 1000000; + + OSAL_THREAD_HANDLE cyclicThread; + OSAL_THREAD_HANDLE checkThread; + + // PDO 数据指针映射 + std::map inputs; + std::map outputs; + std::map inputSizes; + std::map outputSizes; + std::map outputOffsets; + + // PDO 偏移映射 + std::map pdoOffsets; + + // 辅助函数 + template + void writePdo(uint16_t slaveId, uint16_t offset, T value); + + template + T readPdo(uint16_t slaveId, uint16_t offset); + + // 获取从站的影子缓冲区 + SlaveShadowBuffer* getShadowBuffer(uint16_t slaveId); + + // 线程函数 + static OSAL_THREAD_FUNC_RT cyclicThreadFunc(void* arg); + static OSAL_THREAD_FUNC checkThreadFunc(void* arg); + void runCyclic(); + void runCheck(); +}; + +// ==================== 模板实现(必须放在头文件中) ==================== + +template +void EcMaster::writePdo(uint16_t slaveId, uint16_t offset, T value) { + if (inOp) { + auto* shadow = getShadowBuffer(slaveId); + if (shadow && shadow->data) { + if (offset + sizeof(T) <= shadow->size) { + *(T*)(shadow->data + offset) = value; + shadow->dirty = true; + anyPdoDirty = true; + return; + } + } + } + + // 降级:直接写 IOmap(配置阶段使用) + auto outIt = outputs.find(slaveId); + if (outIt != outputs.end() && outIt->second) { + auto sizeIt = outputSizes.find(slaveId); + if (sizeIt != outputSizes.end() && offset + sizeof(T) <= (uint16_t)sizeIt->second) { + *(T*)(outIt->second + offset) = value; + } + } +} + +template +T EcMaster::readPdo(uint16_t slaveId, uint16_t offset) { + auto it = pdoOffsets.find(slaveId); + if (it == pdoOffsets.end() || offset == 0xFFFF) return 0; + + auto inIt = inputs.find(slaveId); + if (inIt != inputs.end() && inIt->second) { + auto sizeIt = inputSizes.find(slaveId); + if (sizeIt != inputSizes.end() && offset + sizeof(T) <= (uint16_t)sizeIt->second) { + return *(T*)(inIt->second + offset); + } + } + return 0; +} + +#endif // EC_MASTER_H \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_pdo_config.h b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_pdo_config.h new file mode 100644 index 00000000..7f8a919d --- /dev/null +++ b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_pdo_config.h @@ -0,0 +1,75 @@ +// ec_pdo_config.h - 固定 PDO 配置(替代 XML 解析) +#ifndef EC_PDO_CONFIG_H +#define EC_PDO_CONFIG_H + +#include "ec_types.h" + +// 创建固定的 RxPDO 配置(对应 XML 中的 #x1600) +inline EcPdo createFixedRxPdo() { + EcPdo rxPdo; + rxPdo.index = 0x1600; + rxPdo.mapCount = 10; // 9个有效条目 + 1个结束标志 + rxPdo.name = "Outputs"; + rxPdo.isRxPdo = true; + + // 按XML顺序填充 Entry + rxPdo.entries.push_back({0x6040, 0, 16, "Control Word", EcDataType::Uint16}); + rxPdo.entries.push_back({0x607A, 0, 32, "Target Position", EcDataType::Int32}); + rxPdo.entries.push_back({0x60FF, 0, 32, "Target Velocity", EcDataType::Int32}); + rxPdo.entries.push_back({0x6071, 0, 16, "Target Torque", EcDataType::Int16}); + rxPdo.entries.push_back({0x6083, 0, 32, "Profile Acceleration", EcDataType::Uint32}); + rxPdo.entries.push_back({0x6084, 0, 32, "Profile Deceleration", EcDataType::Uint32}); + rxPdo.entries.push_back({0x6081, 0, 32, "Profile Velocity", EcDataType::Uint32}); + rxPdo.entries.push_back({0x6087, 0, 32, "Torque Slope", EcDataType::Uint32}); + rxPdo.entries.push_back({0x6060, 0, 8, "Modes Of Operation", EcDataType::Int8}); + rxPdo.entries.push_back({0x0000, 0, 8, "End Marker", EcDataType::Uint8}); + + return rxPdo; +} + +// 创建固定的 TxPDO 配置(对应 XML 中的 #x1a00) +inline EcPdo createFixedTxPdo() { + EcPdo txPdo; + txPdo.index = 0x1A00; + txPdo.mapCount = 8; // 7个有效条目 + 1个结束标志 + txPdo.name = "Inputs"; + txPdo.isRxPdo = false; + + // 按XML顺序填充 Entry + txPdo.entries.push_back({0x6041, 0, 16, "Status Word", EcDataType::Uint16}); + txPdo.entries.push_back({0x6064, 0, 32, "Actual Position", EcDataType::Int32}); + txPdo.entries.push_back({0x606C, 0, 32, "Actual Velocity", EcDataType::Int32}); + txPdo.entries.push_back({0x6077, 0, 16, "Actual Torque", EcDataType::Int16}); + txPdo.entries.push_back({0x6061, 0, 8, "Mode Of Operation Display", EcDataType::Int8}); + txPdo.entries.push_back({0x603F, 0, 16, "Error Code", EcDataType::Uint16}); + txPdo.entries.push_back({0x6078, 0, 16, "Actual Current Value", EcDataType::Int16}); + txPdo.entries.push_back({0x0000, 0, 8, "End Marker", EcDataType::Uint8}); + + return txPdo; +} + +// 创建完整的从站配置 - 可以直接替换 XML 解析结果 +inline EcSlaveConfig createFixedSlaveConfig(uint16_t slaveId) { + EcSlaveConfig config; + config.slaveId = slaveId; + config.productCode = 0x00000000; + config.revisionNo = 0; + config.name = "Axis (csv,csp,cst)"; + + EcModule module; + module.moduleIdent = 0x119800; // 对应 XML 中的 ModuleIdent="#x119800" + module.name = "dynamic switchbewteen csp/csv"; + + module.rxPdos.push_back(createFixedRxPdo()); + module.txPdos.push_back(createFixedTxPdo()); + + // 记录可用的PDO索引(对应 parser.getAvailableRxPdos/getAvailableTxPdos 的返回值) + config.availableRxPdos.push_back(0x1600); + config.availableTxPdos.push_back(0x1A00); + + config.modules.push_back(module); + + return config; +} + +#endif // EC_PDO_CONFIG_H \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_types.h b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_types.h new file mode 100644 index 00000000..174f2460 --- /dev/null +++ b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_types.h @@ -0,0 +1,111 @@ +// ec_types.h - 公共类型定义 +#ifndef EC_TYPES_H +#define EC_TYPES_H + +#include +#include +#include + +// 数据类型枚举 +enum class EcDataType : uint8_t { + Boolean = 0x01, + Int8 = 0x02, + Int16 = 0x03, + Int32 = 0x04, + Uint8 = 0x05, + Uint16 = 0x06, + Uint32 = 0x07, + Real32 = 0x08, + Real64 = 0x09 +}; + +// 从站状态枚举 +enum class EcState : uint8_t { + Init = 0x01, + PreOp = 0x02, + SafeOp = 0x04, + Operational = 0x08 +}; + +// 操作模式枚举 +enum class EcOperateMode : int8_t { + ProfilePosition = 1, + Velocity = 2, + ProfileVelocity = 3, + ProfileTorque = 4, + Homing = 6, + CyclicSyncPosition = 8, + CyclicSyncVelocity = 9, + CyclicSyncTorque = 10 +}; + +// PDO 条目 +struct EcPdoEntry { + uint16_t index = 0; + uint8_t subindex = 0; + uint8_t bitlen = 0; + std::string name; + EcDataType dataType = EcDataType::Uint8; + + uint32_t makeMapValue() const { + return ((uint32_t)index << 16) | ((uint32_t)subindex << 8) | bitlen; + } +}; + +// PDO +struct EcPdo { + uint16_t index = 0; + uint8_t mapCount = 0; + std::vector entries; + std::string name; + bool isRxPdo = false; + + void updateMapCount() { + mapCount = entries.size(); + } +}; + +// 模块信息 +struct EcModule { + uint32_t moduleIdent = 0; + std::string name; + std::vector rxPdos; + std::vector txPdos; +}; + +// 从站配置信息 +struct EcSlaveConfig { + uint16_t slaveId = 0; + uint32_t productCode = 0; + uint32_t revisionNo = 0; + std::string name; + std::vector modules; + std::vector availableRxPdos; + std::vector availableTxPdos; +}; + +// PDO 偏移映射 +// ec_types.h 或 ec_master.h 中 +struct EcPdoOffsets { + // RxPDO 偏移量 (主站 -> 从站) + uint16_t controlWord = 0xFFFF; // 0x6040 + uint16_t targetPosition = 0xFFFF; // 0x607A + uint16_t targetVelocity = 0xFFFF; // 0x60FF + uint16_t targetTorque = 0xFFFF; // 0x6071 + uint16_t profileAcceleration = 0xFFFF; // 0x6083 (PP模式) + uint16_t profileDeceleration = 0xFFFF; // 0x6084 (PP模式) + uint16_t profileVelocity = 0xFFFF; // 0x6081 (PP模式) + uint16_t torqueSlope = 0xFFFF; // 0x6087 (PT模式) + uint16_t modeOfOperation = 0xFFFF; // 0x6060 + + // TxPDO 偏移量 (从站 -> 主站) + uint16_t statusWord = 0xFFFF; // 0x6041 + uint16_t actualPosition = 0xFFFF; // 0x6064 + uint16_t actualVelocity = 0xFFFF; // 0x606C + uint16_t actualTorque = 0xFFFF; // 0x6077 + uint16_t modeOfOperationDisplay = 0xFFFF;// 0x6061 + uint16_t errorCode = 0xFFFF; // 0x603F + uint16_t actualCurrent = 0xFFFF; // 0x6078 当前电流值 +}; + +#endif // EC_TYPES_H \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_xml_parser.cpp b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_xml_parser.cpp new file mode 100644 index 00000000..c721e3fe --- /dev/null +++ b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_xml_parser.cpp @@ -0,0 +1,317 @@ +// ec_xml_parser.cpp +#include "ec_xml_parser.h" +#include "rapidxml/xml_parser.h" +#include +#include +#include + +using namespace rapidxml; + +static EcDataType stringToDataType(const std::string& typeName) { + if (typeName == "BOOL") return EcDataType::Boolean; + if (typeName == "SINT") return EcDataType::Int8; + if (typeName == "INT") return EcDataType::Int16; + if (typeName == "DINT") return EcDataType::Int32; + if (typeName == "USINT") return EcDataType::Uint8; + if (typeName == "UINT") return EcDataType::Uint16; + if (typeName == "UDINT") return EcDataType::Uint32; + if (typeName == "REAL") return EcDataType::Real32; + return EcDataType::Uint8; +} + +static uint32_t parseHexValue(const std::string& str) { + if (str.empty()) return 0; + if (str[0] == '#' && str[1] == 'x') { + return strtoul(str.c_str() + 2, nullptr, 16); + } + return strtoul(str.c_str(), nullptr, 16); +} + +class EcXmlParser::Impl { +public: + std::map slaveConfigs; + std::unique_ptr xmlDoc; + + bool loadXmlFile(const std::string& path) { + try { + xmlDoc = std::make_unique(path); + return true; + } catch (const std::exception& e) { + printf("Failed to load XML file: %s, error: %s\n", path.c_str(), e.what()); + return false; + } + } + + void parsePdoEntry(const XmlNode& entryNode, EcPdoEntry& entry) { + if (entryNode.hasChild("Index")) { + XmlNode idxNode = entryNode.getChild("Index"); + entry.index = (uint16_t)parseHexValue(idxNode.getNodeValue()); + } + if (entryNode.hasChild("SubIndex")) { + XmlNode subIdxNode = entryNode.getChild("SubIndex"); + entry.subindex = (uint8_t)atoi(subIdxNode.getNodeValue().c_str()); + } + if (entryNode.hasChild("BitLen")) { + XmlNode bitLenNode = entryNode.getChild("BitLen"); + entry.bitlen = (uint8_t)atoi(bitLenNode.getNodeValue().c_str()); + } + if (entryNode.hasChild("Name")) { + XmlNode nameEntryNode = entryNode.getChild("Name"); + entry.name = nameEntryNode.getNodeValue(); + } + if (entryNode.hasChild("DataType")) { + XmlNode dataTypeNode = entryNode.getChild("DataType"); + entry.dataType = stringToDataType(dataTypeNode.getNodeValue()); + } + } + + void parsePdoMapping(const XmlNode& pdoNode, bool isRxPdo, EcModule& module) { + if (!pdoNode.hasChild("Index")) { + printf("Warning: PDO missing Index node, skipping\n"); + return; + } + + XmlNode indexNode = pdoNode.getChild("Index"); + uint16_t index = (uint16_t)parseHexValue(indexNode.getNodeValue()); + + EcPdo pdo; + pdo.index = index; + pdo.isRxPdo = isRxPdo; + + if (pdoNode.hasChild("Name")) { + XmlNode nameNode = pdoNode.getChild("Name"); + pdo.name = nameNode.getNodeValue(); + } + + std::vector entries = pdoNode.getChildren("Entry"); + for (const auto& entryNode : entries) { + EcPdoEntry entry; + parsePdoEntry(entryNode, entry); + pdo.entries.push_back(entry); + } + + pdo.updateMapCount(); + + if (isRxPdo) { + module.rxPdos.push_back(pdo); + } else { + module.txPdos.push_back(pdo); + } + } + + void parseModule(const XmlNode& moduleNode, EcSlaveConfig& config) { + if (!moduleNode.hasChild("Type")) { + printf("Warning: Module missing Type node, skipping\n"); + return; + } + + auto typeNode = moduleNode.getChild("Type"); + std::string moduleIdentStr = typeNode.getAttrDefault("ModuleIdent", ""); + if (moduleIdentStr.empty()) { + printf("Warning: Module missing ModuleIdent attribute, skipping\n"); + return; + } + + EcModule module; + module.moduleIdent = (uint32_t)parseHexValue(moduleIdentStr); + + if (moduleNode.hasChild("Name")) { + XmlNode nameNode = moduleNode.getChild("Name"); + module.name = nameNode.getNodeValue(); + } + + if (moduleNode.hasChild("RxPdo")) { + auto rxPdoNode = moduleNode.getChild("RxPdo"); + parsePdoMapping(rxPdoNode, true, module); + } + + if (moduleNode.hasChild("TxPdo")) { + auto txPdoNode = moduleNode.getChild("TxPdo"); + parsePdoMapping(txPdoNode, false, module); + } + + config.modules.push_back(module); + + for (const auto& pdo : module.rxPdos) { + config.availableRxPdos.push_back(pdo.index); + } + for (const auto& pdo : module.txPdos) { + config.availableTxPdos.push_back(pdo.index); + } + + printf(" Parsed module: 0x%08X - %s\n", module.moduleIdent, module.name.c_str()); + printf(" RxPDOs: %zu, TxPDOs: %zu\n", module.rxPdos.size(), module.txPdos.size()); + } + + bool parseSlaveConfigById(uint16_t slaveId, EcSlaveConfig& outConfig) { + if (!xmlDoc) return false; + + XmlNode rootNode = xmlDoc->getRoot(); + if (rootNode.getNodeName() != "EtherCATInfo") { + printf("Root node is not EtherCATInfo\n"); + return false; + } + + if (!rootNode.hasChild("Descriptions")) { + printf("No Descriptions node found\n"); + return false; + } + XmlNode descNode = rootNode.getChild("Descriptions"); + + outConfig = EcSlaveConfig(); + outConfig.slaveId = slaveId; + + if (descNode.hasChild("Devices")) { + XmlNode devicesNode = descNode.getChild("Devices"); + if (devicesNode.hasChild("Device")) { + XmlNode deviceNode = devicesNode.getChild("Device"); + if (deviceNode.hasChild("Type")) { + XmlNode typeNode = deviceNode.getChild("Type"); + std::string productCode = typeNode.getAttrDefault("ProductCode", ""); + if (!productCode.empty()) { + outConfig.productCode = (uint32_t)parseHexValue(productCode); + } + std::string revision = typeNode.getAttrDefault("RevisionNo", ""); + if (!revision.empty()) { + outConfig.revisionNo = (uint32_t)atoi(revision.c_str()); + } + } + if (deviceNode.hasChild("Name")) { + XmlNode nameNode = deviceNode.getChild("Name"); + outConfig.name = nameNode.getNodeValue(); + } + } + } + + if (!descNode.hasChild("Modules")) { + printf("Warning: No Modules node found under Descriptions\n"); + return !outConfig.modules.empty(); + } + + XmlNode modulesNode = descNode.getChild("Modules"); + printf("Found Modules node under Descriptions, parsing PDO configurations...\n"); + + std::vector moduleNodes = modulesNode.getChildren("Module"); + if (moduleNodes.empty()) { + printf("Warning: No Module element found under Modules\n"); + return !outConfig.modules.empty(); + } + + for (const auto& moduleNode : moduleNodes) { + parseModule(moduleNode, outConfig); + } + + printf("Total %zu modules parsed\n", moduleNodes.size()); + return true; + } +}; + +// ==================== EcXmlParser 公共接口 ==================== + +EcXmlParser::EcXmlParser() : pImpl(std::make_unique()) {} +EcXmlParser::~EcXmlParser() = default; + +bool EcXmlParser::loadXmlFile(const std::string& xmlPath) { + return pImpl->loadXmlFile(xmlPath); +} + +bool EcXmlParser::parseSlaveConfig(uint16_t slaveId, EcSlaveConfig& outConfig) { + if (!pImpl->parseSlaveConfigById(slaveId, outConfig)) { + return false; + } + pImpl->slaveConfigs[slaveId] = outConfig; + return true; +} + +EcSlaveConfig* EcXmlParser::getSlaveConfig(uint16_t slaveId) { + auto it = pImpl->slaveConfigs.find(slaveId); + return (it != pImpl->slaveConfigs.end()) ? &it->second : nullptr; +} + +const EcSlaveConfig* EcXmlParser::getSlaveConfig(uint16_t slaveId) const { + auto it = pImpl->slaveConfigs.find(slaveId); + return (it != pImpl->slaveConfigs.end()) ? &it->second : nullptr; +} + +std::vector EcXmlParser::getAvailableRxPdos(uint16_t slaveId) const { + auto it = pImpl->slaveConfigs.find(slaveId); + return (it != pImpl->slaveConfigs.end()) ? it->second.availableRxPdos : std::vector(); +} + +std::vector EcXmlParser::getAvailableTxPdos(uint16_t slaveId) const { + auto it = pImpl->slaveConfigs.find(slaveId); + return (it != pImpl->slaveConfigs.end()) ? it->second.availableTxPdos : std::vector(); +} + +const EcPdo* EcXmlParser::findPdo(uint16_t slaveId, uint16_t pdoIndex, bool isRxPdo) const { + auto it = pImpl->slaveConfigs.find(slaveId); + if (it == pImpl->slaveConfigs.end()) return nullptr; + + for (const auto& module : it->second.modules) { + const auto& pdos = isRxPdo ? module.rxPdos : module.txPdos; + for (const auto& pdo : pdos) { + if (pdo.index == pdoIndex) return &pdo; + } + } + return nullptr; +} + +const EcModule* EcXmlParser::findModuleByIndex(uint16_t slaveId, uint16_t pdoIndex, bool isRxPdo) const { + auto it = pImpl->slaveConfigs.find(slaveId); + if (it == pImpl->slaveConfigs.end()) return nullptr; + + for (const auto& module : it->second.modules) { + const auto& pdos = isRxPdo ? module.rxPdos : module.txPdos; + for (const auto& pdo : pdos) { + if (pdo.index == pdoIndex) return &module; + } + } + return nullptr; +} + +const EcModule* EcXmlParser::findModuleByIdent(uint16_t slaveId, uint32_t moduleIdent) const { + auto it = pImpl->slaveConfigs.find(slaveId); + if (it == pImpl->slaveConfigs.end()) return nullptr; + + for (const auto& module : it->second.modules) { + if (module.moduleIdent == moduleIdent) return &module; + } + return nullptr; +} + +void EcXmlParser::printSlaveInfo(uint16_t slaveId) const { + auto it = pImpl->slaveConfigs.find(slaveId); + if (it == pImpl->slaveConfigs.end()) { + printf("Slave %d not configured\n", slaveId); + return; + } + + const EcSlaveConfig& config = it->second; + printf("\n========== Slave %d Configuration ==========\n", slaveId); + printf("Name: %s\n", config.name.c_str()); + printf("Product Code: 0x%08X\n", config.productCode); + printf("Revision: %u\n", config.revisionNo); + + for (const auto& module : config.modules) { + printf("\n[Module 0x%08X] %s\n", module.moduleIdent, module.name.c_str()); + for (const auto& pdo : module.rxPdos) { + printf(" RxPDO 0x%04X - %s (%d entries)\n", pdo.index, pdo.name.c_str(), pdo.mapCount); + for (const auto& entry : pdo.entries) { + printf(" -> 0x%04X:%02X (%d bits) - %s\n", + entry.index, entry.subindex, entry.bitlen, entry.name.c_str()); + } + } + for (const auto& pdo : module.txPdos) { + printf(" TxPDO 0x%04X - %s (%d entries)\n", pdo.index, pdo.name.c_str(), pdo.mapCount); + for (const auto& entry : pdo.entries) { + printf(" <- 0x%04X:%02X (%d bits) - %s\n", + entry.index, entry.subindex, entry.bitlen, entry.name.c_str()); + } + } + } + printf("==========================================\n"); +} + +void EcXmlParser::clear() { + pImpl->slaveConfigs.clear(); +} \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_xml_parser.h b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_xml_parser.h new file mode 100644 index 00000000..c7b72536 --- /dev/null +++ b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_xml_parser.h @@ -0,0 +1,41 @@ +// ec_xml_parser.h +#ifndef EC_XML_PARSER_H +#define EC_XML_PARSER_H + +#include "ec_types.h" +#include +#include +#include + +// XML PDO配置解析器类(仅负责解析XML) +class EcXmlParser { +public: + EcXmlParser(); + ~EcXmlParser(); + + bool loadXmlFile(const std::string& xmlPath); + bool parseSlaveConfig(uint16_t slaveId, EcSlaveConfig& outConfig); + + // 获取已解析的配置 + EcSlaveConfig* getSlaveConfig(uint16_t slaveId); + const EcSlaveConfig* getSlaveConfig(uint16_t slaveId) const; + + // 查询接口 + std::vector getAvailableRxPdos(uint16_t slaveId) const; + std::vector getAvailableTxPdos(uint16_t slaveId) const; + const EcPdo* findPdo(uint16_t slaveId, uint16_t pdoIndex, bool isRxPdo) const; + const EcModule* findModuleByIndex(uint16_t slaveId, uint16_t pdoIndex, bool isRxPdo) const; + const EcModule* findModuleByIdent(uint16_t slaveId, uint32_t moduleIdent) const; + + // 打印信息 + void printSlaveInfo(uint16_t slaveId) const; + + // 清空所有配置 + void clear(); + +private: + class Impl; + std::unique_ptr pImpl; +}; + +#endif // EC_XML_PARSER_H \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/eyou_motor.cpp b/cmvr-es/devices/motor/eyou_motor/eyou_motor.cpp new file mode 100644 index 00000000..d9e2f043 --- /dev/null +++ b/cmvr-es/devices/motor/eyou_motor/eyou_motor.cpp @@ -0,0 +1,5 @@ +// +// Created by cmvr on 2026/3/30. +// + +#include "eyou_motor.h" \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/eyou_motor.h b/cmvr-es/devices/motor/eyou_motor/eyou_motor.h new file mode 100644 index 00000000..da4d8ae3 --- /dev/null +++ b/cmvr-es/devices/motor/eyou_motor/eyou_motor.h @@ -0,0 +1,46 @@ +// +// Created by cmvr on 2026/3/30. +// +#pragma once +#include "../abstract_motor.h" +#include "motor/motor_protocol_interface.h" +#include "motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.h" + +namespace cmvr { + namespace device { + class EYouMotor : public AbstractMotor { + public: + EYouMotor(const XmlNode &config,uint8_t node_id):AbstractMotor(config,node_id){} + EYouMotor(const XmlNode &config):AbstractMotor(config) { + info_.id = config.getAttrDefault("slave", -1); + info_.joint_name = config.getAttrString("jointName"); + info_.limitQ = config.getAttrDefault("limitQ", 3.14f); + info_.limitQd = config.getAttrDefault("limitQd", 0.5f); + node_id_ = info_.id; + } + + EYouMotor(const cmvr::config::EthercatMotorConfig& config) : AbstractMotor() { + info_.id = config.slave(); + info_.joint_name = config.jointname(); + info_.limitQ = config.limitqlb(); + info_.limitQd = config.limitqd(); + node_id_ = info_.id; + } + /** + * 电机初始化在具体的类中完成 + */ + void init() override { + if (!protocol_) { + throw std::runtime_error("Protocol not set for motor"); + } + if (protocol_->comm_proto == MotorProtocolInterface::CommProto::ETHERCAT ) { + auto ethercat_protocol = std::dynamic_pointer_cast(protocol_); + ethercat_protocol->initNode(node_id_); + ethercat_protocol->setQdd(node_id_,1); + } + } + }; + + + } +} diff --git a/cmvr-es/devices/motor/motor_protocol_interface.h b/cmvr-es/devices/motor/motor_protocol_interface.h index 634a715c..6b49aa06 100644 --- a/cmvr-es/devices/motor/motor_protocol_interface.h +++ b/cmvr-es/devices/motor/motor_protocol_interface.h @@ -15,7 +15,8 @@ namespace cmvr { public: enum class CommProto : uint8_t { CANOPEN = 1, - CUSTOM = 2 + ETHERCAT = 2, + CUSTOM = 3 }; virtual ~MotorProtocolInterface() = default; diff --git a/cmvr-es/devices/motor/ti5_motor/ti5_motor.h b/cmvr-es/devices/motor/ti5_motor/ti5_motor.h index cfc9bde9..65f44ddc 100644 --- a/cmvr-es/devices/motor/ti5_motor/ti5_motor.h +++ b/cmvr-es/devices/motor/ti5_motor/ti5_motor.h @@ -1,11 +1,12 @@ // // Created by lgv on 2025/8/1. // +#pragma once #include "../abstract_motor.h" #include "motor/motor_protocol_interface.h" #include "motor/ti5_motor/canopen/ti5_motor_canopen_protocol.h" - +#include "cmvr/config/robot_config/robot_config.pb.h" /** * class: Ti5Motor 完成电机硬件的相关工作,软件相关的在protocol 中完成 */ @@ -22,6 +23,15 @@ namespace cmvr { node_id_ = info_.id; } + Ti5Motor(const cmvr::config::MotorConfig& config) : AbstractMotor() { + info_.id = config.id(); + info_.joint_name = config.jointname(); + info_.limitQ = config.limitqlb(); + info_.limitQd = config.limitqd(); + node_id_ = info_.id; + } + + /** * 电机初始化在具体的类中完成 */ diff --git a/cmvr-es/devices/robot/abstract_robot.h b/cmvr-es/devices/robot/abstract_robot.h index 5759c3af..91daf0ae 100644 --- a/cmvr-es/devices/robot/abstract_robot.h +++ b/cmvr-es/devices/robot/abstract_robot.h @@ -41,6 +41,7 @@ namespace cmvr::device{ class AbstractRobot: public AbstractDevice { public: + AbstractRobot() = default; explicit AbstractRobot(const XmlNode &config): AbstractDevice(config) {} ~AbstractRobot() override=default; diff --git a/cmvr-es/devices/robot/humanoid_robot/include/humanoid_robot.h b/cmvr-es/devices/robot/humanoid_robot/include/humanoid_robot.h index 7770997b..9cd2d11d 100644 --- a/cmvr-es/devices/robot/humanoid_robot/include/humanoid_robot.h +++ b/cmvr-es/devices/robot/humanoid_robot/include/humanoid_robot.h @@ -35,6 +35,8 @@ #include "planner/joint_space_planner/include/joint_space_planner_creator.h" #include "planner/joint_space_planner/include/joint_space_planner.h" #include "ik_solver/include/pinocchio_dls_ik_solver.h" +#include "cmvr/config/robot_config/robot_config.pb.h" + namespace cmvr::device{ @@ -49,12 +51,10 @@ namespace cmvr::device{ ROBOT_ERROR } RobotStateMachine; -// typedef enum { -// PPM_MODE, PVM_MODE, PTM_MODE, CSP_MODE, CSV_MODE, CSC_MODE -// } RobotRunningMode; - public: explicit HumanoidRobot(const XmlNode& cfg); + HumanoidRobot(const cmvr::config::HumanRobotConfig& config); + HumanoidRobot(const cmvr::config::EthercatRobotConfig& config); ~HumanoidRobot(); void init() override; @@ -232,6 +232,7 @@ namespace cmvr::device{ std::atomic is_head_busy_{false}, is_waist_busy_{false}; + std::string urdf_path_ = ""; }; diff --git a/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot.cpp b/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot.cpp index 0146c053..601125cc 100644 --- a/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot.cpp +++ b/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot.cpp @@ -49,6 +49,87 @@ HumanoidRobot::HumanoidRobot(const XmlNode &cfg) : AbstractRobot(cfg) { } } + +template +HumanoidRobot::HumanoidRobot(const cmvr::config::HumanRobotConfig& config) { + try { + id_ = config.id(); + urdf_path_ = config.urdf(); + dof_ = DOF; + if (!pathExists(config.urdf())) { + throw runtime_error("urdf file does not exist"); + } + auto rcfg = cmvr::dyn::LoadRobotFromURDF( + config.urdf(), config.baselink()); + m_robot_ = std::make_shared >(rcfg); + + // 解析jointNames字符串,按逗号分隔 + joint_names_ = splitString(config.jointnames(), ","); + // 解析linkNames字符串,按逗号分隔 + link_names_ = splitString(config.linknames(), ","); + + if (joint_names_.size() != dof_) { + throw runtime_error("joint names size mismatched with dof"); + } + + m_state_ = m_robot_->MakeState(link_names_, joint_names_); + m_cctrl_ = make_shared >(m_robot_); + upd_freq_ = config.updfreq(); + CSP_buffer_ = make_shared >(config.buffersize()); + CSV_buffer_ = make_shared >(config.buffersize()); + CSC_buffer_ = make_shared >(config.buffersize()); + + upd_freq_ = 1000; + upd_timer_ = make_shared(); + upd_timer_->start(chrono::nanoseconds(1000 / upd_freq_ * 1000), + [this] { update_state_(); }); + rsm_.store(ROBOT_READY); + } catch (exception &e) { + LOG(ERROR) << "HumanoidRobot init failed, id=" << id_; + throw runtime_error(e.what()); + } +} + +template +HumanoidRobot::HumanoidRobot(const cmvr::config::EthercatRobotConfig& config) { + try { + id_ = config.id(); + urdf_path_ = config.urdf(); + dof_ = DOF; + if (!pathExists(config.urdf())) { + throw runtime_error("urdf file does not exist"); + } + auto rcfg = cmvr::dyn::LoadRobotFromURDF( + config.urdf(), config.baselink()); + m_robot_ = std::make_shared >(rcfg); + + // 解析jointNames字符串,按逗号分隔 + joint_names_ = splitString(config.jointnames(), ","); + // 解析linkNames字符串,按逗号分隔 + link_names_ = splitString(config.linknames(), ","); + + if (joint_names_.size() != dof_) { + throw runtime_error("joint names size mismatched with dof"); + } + + m_state_ = m_robot_->MakeState(link_names_, joint_names_); + m_cctrl_ = make_shared >(m_robot_); + upd_freq_ = config.updfreq(); + CSP_buffer_ = make_shared >(config.buffersize()); + CSV_buffer_ = make_shared >(config.buffersize()); + CSC_buffer_ = make_shared >(config.buffersize()); + + upd_freq_ = 1000; + upd_timer_ = make_shared(); + upd_timer_->start(chrono::nanoseconds(1000 / upd_freq_ * 1000), + [this] { update_state_(); }); + rsm_.store(ROBOT_READY); + } catch (exception &e) { + LOG(ERROR) << "HumanoidRobot init failed, id=" << id_; + throw runtime_error(e.what()); + } +} + template void HumanoidRobot::init() { motor_manager_ = MotorsInfo::getInstance()->getMotorManager(); diff --git a/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot_test.cpp b/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot_test.cpp index d1b47e4e..ed16d61d 100644 --- a/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot_test.cpp +++ b/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot_test.cpp @@ -990,3 +990,21 @@ TEST(HumanoidRobotTest,IBVSWithRealRobotTrackedPoint) { EXPECT_GT(ok_steps, 0) << "No successful tracked-point IBVS control steps."; } + + +TEST(HumanoidRobotTest, EYouTest) { + auto& dmgr = DeviceManager::getInstance(); + auto robot_abs = dmgr.getDevice("ec_robot_01"); + auto robot = std::dynamic_pointer_cast>(robot_abs); + ASSERT_NE(robot, nullptr) << "hc01 is not HumanoidRobot<14>"; + + std::vector init_cmd = { + {"L_SHOULDER_P", 0.1} + }; + // robot->moveJ(init_cmd, 0.1, 1.0); + + // robot->calibrateZeroQ("L_SHOULDER_P"); + + robot->torqueOff("L_SHOULDER_P"); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); +} diff --git a/cmvr-es/main.cpp b/cmvr-es/main.cpp index ab0f76d3..24f189a5 100644 --- a/cmvr-es/main.cpp +++ b/cmvr-es/main.cpp @@ -9,6 +9,7 @@ #include #include "service/grpc/include/server_runner.h" #include "utils/base/include/logger.h" +#include "common/utils/config_helper/include/config_helper.h" using namespace cmvr::service; int main(int argc, char* argv[]) { std::string config_path; diff --git a/cmvr-es/monitor/abstract_monitor.h b/cmvr-es/monitor/abstract_monitor.h index 6b707e9c..244e5722 100644 --- a/cmvr-es/monitor/abstract_monitor.h +++ b/cmvr-es/monitor/abstract_monitor.h @@ -13,6 +13,7 @@ namespace cmvr::monitor{ class AbstractMonitor { public: + AbstractMonitor() = default; explicit AbstractMonitor(const XmlNode &cfg){ cfg_ = cfg; freq_ = cfg_.getAttrDefault("freq", 1); diff --git a/cmvr-es/monitor/diskmonitor/include/disk_monitor.h b/cmvr-es/monitor/diskmonitor/include/disk_monitor.h index 89664362..a22256b3 100644 --- a/cmvr-es/monitor/diskmonitor/include/disk_monitor.h +++ b/cmvr-es/monitor/diskmonitor/include/disk_monitor.h @@ -8,17 +8,21 @@ #include #include "../../abstract_monitor.h" +#include "cmvr/config/monitor_config/monitor_config.pb.h" namespace cmvr::monitor { class DiskMonitor final : public AbstractMonitor{ public: explicit DiskMonitor(const XmlNode& config); + explicit DiskMonitor(const cmvr::config::DiskMonitorConfig& config); // 新增proto构造函数 ~DiskMonitor(); protected: bool check_() override; bool execution_() override; private: + void initFromProto(const cmvr::config::DiskMonitorConfig& config); // 新增初始化方法 + std::unordered_map folders_; std::vector dirsToClean_; // 需要清理的目录列表 mutable std::mutex mutex_; // 保护共享资源的互斥锁 @@ -27,6 +31,4 @@ namespace cmvr::monitor { }; } - - #endif //FOLDER_MONITOR_H \ No newline at end of file diff --git a/cmvr-es/monitor/diskmonitor/src/disk_monitor.cpp b/cmvr-es/monitor/diskmonitor/src/disk_monitor.cpp index 20630615..9f4497cf 100644 --- a/cmvr-es/monitor/diskmonitor/src/disk_monitor.cpp +++ b/cmvr-es/monitor/diskmonitor/src/disk_monitor.cpp @@ -14,7 +14,6 @@ using namespace cmvr::monitor; namespace fs = std::filesystem; using namespace std::chrono; - // 文件信息结构体 struct FileInfo { std::string path; @@ -38,51 +37,40 @@ bool deleteOldestFile(const std::string& directoryPath) { struct dirent* entry; struct stat fileStat; - // 遍历目录中的每个条目 while ((entry = readdir(dir)) != nullptr) { - // 跳过 "." 和 ".." if (std::string(entry->d_name) == "." || std::string(entry->d_name) == "..") { continue; } - // 构建完整路径 std::string filePath = directoryPath + "/" + entry->d_name; - // 获取文件状态信息 if (stat(filePath.c_str(), &fileStat) == -1) { std::cerr << "Failed to get file status: " << filePath << std::endl; continue; } - // 只处理普通文件 if (S_ISREG(fileStat.st_mode)) { FileInfo fileInfo; fileInfo.path = filePath; - - // 优先使用 st_birthtime (如果可用),否则使用 st_ctime #ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME fileInfo.creationTime = fileStat.st_birthtime; #else fileInfo.creationTime = fileStat.st_ctime; #endif - files.push_back(fileInfo); } } closedir(dir); - // 如果没有找到文件,返回失败 if (files.empty()) { std::cout << "No files found in directory: " << directoryPath << std::endl; return false; } - // 按创建时间排序 std::sort(files.begin(), files.end(), compareByCreationTime); - - // 删除最早创建的文件 std::string oldestFilePath = files[0].path; + if (unlink(oldestFilePath.c_str()) == -1) { std::cerr << "Failed to delete file: " << oldestFilePath << std::endl; return false; @@ -92,11 +80,11 @@ bool deleteOldestFile(const std::string& directoryPath) { return true; } - bool pathExists(const std::string& path) { struct stat buffer; return (stat(path.c_str(), &buffer) == 0); } + int countFilesInDirectory(const std::string& path) { DIR* dir = opendir(path.c_str()); if (!dir) { @@ -107,9 +95,7 @@ int countFilesInDirectory(const std::string& path) { int count = 0; struct dirent* entry; - // 遍历目录中的每个条目 while ((entry = readdir(dir)) != nullptr) { - // 跳过 "." 和 ".." if (std::string(entry->d_name) != "." && std::string(entry->d_name) != "..") { count++; } @@ -119,8 +105,8 @@ int countFilesInDirectory(const std::string& path) { return count; } +// XML构造函数 DiskMonitor::DiskMonitor(const XmlNode& config) : AbstractMonitor(config){ - try { for (auto &node: config.getChildren()){ string fileDir = node.getAttrString("fileDir"); @@ -138,13 +124,42 @@ DiskMonitor::DiskMonitor(const XmlNode& config) : AbstractMonitor(config){ throw runtime_error("[DiskMonitor] (DiskMonitor): Failed "+ string(e.what())); } } + +// Proto构造函数 +DiskMonitor::DiskMonitor(const cmvr::config::DiskMonitorConfig& config) : AbstractMonitor() { + try { + initFromProto(config); + is_initialized_ = true; + LOG(INFO) << "[DiskMonitor]: Init DiskMonitor from proto Success"; + } + catch (const exception& e) { + LOG(ERROR) << "[DiskMonitor] (DiskMonitor): Failed " << e.what(); + throw runtime_error("[DiskMonitor] (DiskMonitor): Failed " + string(e.what())); + } +} + +// Proto初始化方法 +void DiskMonitor::initFromProto(const cmvr::config::DiskMonitorConfig& config) { + // 设置监控ID和频率(通过AbstractMonitor) + // 假设AbstractMonitor有setId和setFreq方法,如果没有需要添加 + // 这里根据你的AbstractMonitor实现来调整 + + for (const auto& folder : config.folders()) { + string fileDir = folder.filedir(); + if (!pathExists(fileDir)) { + throw runtime_error("[DiskMonitor] (initFromProto): path not exists: " + fileDir); + } + int maxVolume = folder.maxvolume(); + folders_[fileDir] = maxVolume; + } +} + DiskMonitor::~DiskMonitor() { folders_.clear(); } + bool DiskMonitor::check_() { - // 检查当前目录下的文件数量是否超过最大值 bool allOk = true; - // 遍历所有监控的文件夹 for (const auto& folder : folders_) { const std::string& path = folder.first; size_t maxFiles = folder.second; @@ -154,28 +169,21 @@ bool DiskMonitor::check_() { continue; } int nFileCount = countFilesInDirectory(path); - if (nFileCount > maxFiles) - { + if (nFileCount > maxFiles) { allOk = false; dirsToClean_.push_back(path); std::cout << "path need remove oldest file: " << path << std::endl; } - - } catch (const fs::filesystem_error& e) { std::cerr << "Filesystem error: " << e.what() << '\n'; } catch (const std::exception& e) { std::cerr << "Unexpected error: " << e.what() << '\n'; } } - return allOk; } - bool DiskMonitor::execution_() { - - // 删除创建时间最早的文件 std::lock_guard lock(mutex_); bool success = true; @@ -183,6 +191,6 @@ bool DiskMonitor::execution_() { deleteOldestFile(path); } - dirsToClean_.clear(); // 清理完成后清空列表 + dirsToClean_.clear(); return success; } \ No newline at end of file diff --git a/cmvr-es/monitor_manager/include/monitor_factory.h b/cmvr-es/monitor_manager/include/monitor_factory.h index a0dbc900..cdd879a3 100644 --- a/cmvr-es/monitor_manager/include/monitor_factory.h +++ b/cmvr-es/monitor_manager/include/monitor_factory.h @@ -5,8 +5,8 @@ #ifndef MONITOR_FACTORY_H #define MONITOR_FACTORY_H - -#include "../../monitor/abstract_monitor.h" +#include "monitor/abstract_monitor.h" +#include "cmvr/config/monitor_config/monitor_config.pb.h" namespace cmvr::monitor { @@ -14,14 +14,17 @@ namespace cmvr::monitor { public: MonitorFactory() = default; + + // 原有的XML方式 template std::shared_ptr create(const XmlNode& cfg); std::shared_ptr create(const XmlNode& cfg); + + // 新增proto方式 + template + std::shared_ptr create(const cmvr::config::DiskMonitorConfig& config); + std::shared_ptr create(const cmvr::config::DiskMonitorConfig& config); }; - - } - - #endif //MONITOR_FACTORY_H \ No newline at end of file diff --git a/cmvr-es/monitor_manager/include/monitor_manager.h b/cmvr-es/monitor_manager/include/monitor_manager.h index 8c53a401..422ece1f 100644 --- a/cmvr-es/monitor_manager/include/monitor_manager.h +++ b/cmvr-es/monitor_manager/include/monitor_manager.h @@ -15,23 +15,38 @@ namespace cmvr::monitor { public: MonitorManager(const MonitorManager&) = delete; MonitorManager& operator=(const MonitorManager&) = delete; + + // 原有的XML方式 static MonitorManager& getInstance(const XmlNode &cfg); + + // 新增proto方式 + static MonitorManager& getInstance(const cmvr::config::MonitorManagerConfig& config); + static MonitorManager& getInstance(); static void destroyInstance(); private: - explicit MonitorManager (const XmlNode &cfg); - void init_monitors(); + // 原有的XML方式 + explicit MonitorManager(const XmlNode &cfg); + + // 新增proto方式 + explicit MonitorManager(const cmvr::config::MonitorManagerConfig& config); + + void init_monitors(); // XML方式 + void init_monitors_from_proto(); // Proto方式 + private: static std::once_flag init_flag_; static std::shared_ptr instance_; - XmlNode cfg_; + // 两种配置方式,只有一个有效 + bool use_proto_ = false; + XmlNode cfg_; // XML配置 + cmvr::config::MonitorManagerConfig proto_cfg_; // Proto配置 + std::shared_ptr monitor_factory_; std::unordered_map> monitors_; }; } - - #endif //MONITOR_MANAGER_H \ No newline at end of file diff --git a/cmvr-es/monitor_manager/src/monitor_factory.cpp b/cmvr-es/monitor_manager/src/monitor_factory.cpp index 821373e2..e770f294 100644 --- a/cmvr-es/monitor_manager/src/monitor_factory.cpp +++ b/cmvr-es/monitor_manager/src/monitor_factory.cpp @@ -3,12 +3,12 @@ // #include "../include/monitor_factory.h" - #include "../../monitor/diskmonitor/include/disk_monitor.h" using namespace std; using namespace cmvr::monitor; +// XML方式的模板特化 template std::shared_ptr MonitorFactory::create(const XmlNode&); template @@ -17,8 +17,30 @@ std::shared_ptr MonitorFactory::create(const XmlNode& cfg) { return std::make_shared(cfg); } else { - LOG(ERROR) << "[DeviceFactory]: Unsupported device type"; - throw runtime_error("[DeviceFactory]: Unsupported device type"); + LOG(ERROR) << "[MonitorFactory]: Unsupported monitor type"; + throw runtime_error("[MonitorFactory]: Unsupported monitor type"); } } +// Proto方式的模板特化 +template std::shared_ptr MonitorFactory::create(const cmvr::config::DiskMonitorConfig&); + +template +std::shared_ptr MonitorFactory::create(const cmvr::config::DiskMonitorConfig& config) { + if constexpr (std::is_same_v) { + return std::make_shared(config); + } + else { + LOG(ERROR) << "[MonitorFactory]: Unsupported monitor type"; + throw runtime_error("[MonitorFactory]: Unsupported monitor type"); + } +} + +std::shared_ptr MonitorFactory::create(const XmlNode& cfg) { + // 保留原有实现 + return nullptr; +} + +std::shared_ptr MonitorFactory::create(const cmvr::config::DiskMonitorConfig& config) { + return std::make_shared(config); +} \ No newline at end of file diff --git a/cmvr-es/monitor_manager/src/monitor_manager.cpp b/cmvr-es/monitor_manager/src/monitor_manager.cpp index 2dd9e2cf..dde9b386 100644 --- a/cmvr-es/monitor_manager/src/monitor_manager.cpp +++ b/cmvr-es/monitor_manager/src/monitor_manager.cpp @@ -3,8 +3,7 @@ // #include "../include/monitor_manager.h" - -#include "../../monitor/diskmonitor/include/disk_monitor.h" +#include "monitor/diskmonitor/include/disk_monitor.h" using namespace std; using namespace cmvr::monitor; @@ -12,11 +11,19 @@ using namespace cmvr::monitor; std::shared_ptr MonitorManager::instance_ = nullptr; std::once_flag MonitorManager::init_flag_; -MonitorManager::MonitorManager(const XmlNode& cfg) { +// XML方式构造函数 +MonitorManager::MonitorManager(const XmlNode& cfg) : use_proto_(false), cfg_(cfg) { monitor_factory_ = make_shared(); - cfg_ = cfg; init_monitors(); } + +// Proto方式构造函数 +MonitorManager::MonitorManager(const cmvr::config::MonitorManagerConfig& config) : use_proto_(true), proto_cfg_(config) { + monitor_factory_ = make_shared(); + init_monitors_from_proto(); +} + +// XML方式单例 MonitorManager& MonitorManager::getInstance(const XmlNode& cfg) { std::call_once(init_flag_, [&cfg] { instance_.reset(new MonitorManager(cfg)); @@ -24,9 +31,17 @@ MonitorManager& MonitorManager::getInstance(const XmlNode& cfg) { return *instance_; } +// Proto方式单例 +MonitorManager& MonitorManager::getInstance(const cmvr::config::MonitorManagerConfig& config) { + std::call_once(init_flag_, [&config] { + instance_.reset(new MonitorManager(config)); + }); + return *instance_; +} + MonitorManager& MonitorManager::getInstance() { if (!instance_) { - throw std::runtime_error("[MonitorManager] (getInstance): MonitorManager not initialized. Call getInstance(const XmlNode&) first."); + throw std::runtime_error("[MonitorManager] (getInstance): MonitorManager not initialized. Call getInstance() first."); } return *instance_; } @@ -35,13 +50,14 @@ void MonitorManager::destroyInstance() { instance_.reset(); } +// XML方式初始化 void MonitorManager::init_monitors() { - try{ + try { auto diskmonitor_node = cfg_.getChild("DiskMonitor"); auto id = diskmonitor_node.getAttrString("id"); auto monitor = monitor_factory_->create(diskmonitor_node); if (monitor == nullptr) { - throw std::runtime_error("[DeviceManager]: Factory returned nullptr for device ID: " + id); + throw std::runtime_error("[MonitorManager]: Factory returned nullptr for monitor ID: " + id); } monitors_[id] = monitor; } @@ -49,5 +65,31 @@ void MonitorManager::init_monitors() { LOG(ERROR) << e.what(); throw runtime_error(e.what()); } +} +// Proto方式初始化 +void MonitorManager::init_monitors_from_proto() { + try { + if (!proto_cfg_.has_disk_monitor()) { + LOG(WARNING) << "[MonitorManager]: No disk_monitor config found"; + return; + } + + const auto& disk_monitor_cfg = proto_cfg_.disk_monitor(); + if (!disk_monitor_cfg.enable()) { + LOG(INFO) << "[MonitorManager]: Disk monitor is disabled"; + return; + } + + auto id = disk_monitor_cfg.id(); + auto monitor = monitor_factory_->create(disk_monitor_cfg); + if (monitor == nullptr) { + throw std::runtime_error("[MonitorManager]: Factory returned nullptr for monitor ID: " + id); + } + monitors_[id] = monitor; + } + catch (const exception& e) { + LOG(ERROR) << e.what(); + throw runtime_error(e.what()); + } } \ No newline at end of file diff --git a/cmvr-es/service/grpc/include/server_runner.h b/cmvr-es/service/grpc/include/server_runner.h index be8eadf8..e148c811 100644 --- a/cmvr-es/service/grpc/include/server_runner.h +++ b/cmvr-es/service/grpc/include/server_runner.h @@ -11,13 +11,14 @@ #include #include #include "rapidxml/xml_parser.h" +#include "cmvr/config/server_config/server_config.pb.h" namespace cmvr::service { class ServerRunner { public: ServerRunner(); - explicit ServerRunner(XmlNode cfg); + ServerRunner(const cmvr::config::ServerConfig& config); ~ServerRunner(); ServerRunner(const ServerRunner&) = delete; @@ -25,7 +26,7 @@ namespace cmvr::service { ServerRunner(ServerRunner&&) = delete; ServerRunner& operator=(ServerRunner&&) = delete; - void start(XmlNode cfg); + void start(); void stop(); void join(); @@ -40,7 +41,7 @@ namespace cmvr::service { std::condition_variable cv_; std::thread worker_; - XmlNode cfg_; + cmvr::config::ServerConfig cfg_; std::unique_ptr server_; std::string address_; diff --git a/cmvr-es/service/grpc/src/grpc_humanoid_robot_service.cpp b/cmvr-es/service/grpc/src/grpc_humanoid_robot_service.cpp index 487fe705..0c4e8469 100644 --- a/cmvr-es/service/grpc/src/grpc_humanoid_robot_service.cpp +++ b/cmvr-es/service/grpc/src/grpc_humanoid_robot_service.cpp @@ -17,7 +17,7 @@ using google::protobuf::util::TimeUtil; gRPCHumanoidRobotServiceImpl::gRPCHumanoidRobotServiceImpl():dmgr_(DeviceManager::getInstance()) { - pinocchio_ik_ = std::make_shared("/home/linbo/cmvr/cmvr-es/model/xiaoyan_description/dual_arm.urdf" + pinocchio_ik_ = std::make_shared("/home/cmvr/Projects/cmvr-es/model/xiaoyan_description/dual_arm.urdf" ,"PELVIS_S" ,"R_WRIST_R_S"); pinocchio_ik_->init(); diff --git a/cmvr-es/service/grpc/src/server_runner.cpp b/cmvr-es/service/grpc/src/server_runner.cpp index dc689aff..ec04091a 100644 --- a/cmvr-es/service/grpc/src/server_runner.cpp +++ b/cmvr-es/service/grpc/src/server_runner.cpp @@ -25,23 +25,21 @@ using namespace cmvr::service; ServerRunner::ServerRunner() = default; -ServerRunner::ServerRunner(XmlNode cfg) { - start(std::move(cfg)); +ServerRunner::ServerRunner(const cmvr::config::ServerConfig& config):cfg_(config) +{ + // start(); } - ServerRunner::~ServerRunner() { stop(); join(); } -void ServerRunner::start(XmlNode cfg) { +void ServerRunner::start() { std::unique_lock lk(mtx_); if (worker_.joinable()) { throw std::runtime_error("ServerRunner already started"); } - - cfg_ = std::move(cfg); stop_requested_ = false; started_ = false; start_failed_ = false; @@ -97,25 +95,15 @@ void ServerRunner::threadMain() { using namespace cmvr::monitor; try { - if (!cfg_.hasChild("DeviceManager")) { - throw std::runtime_error("Device Manager node not found"); - } + static std::once_flag reflection_once; std::call_once(reflection_once, []() { grpc::reflection::InitProtoReflectionServerBuilderPlugin(); }); + DeviceManager::getInstance(); - auto dmgr_cfg = cfg_.getChild("DeviceManager"); - DeviceManager::getInstance(dmgr_cfg); - - if (cfg_.hasChild("MonitorManager")) { - auto mmgr_cfg = cfg_.getChild("MonitorManager"); - MonitorManager::getInstance(mmgr_cfg); - } - - auto grpc_cfg = cfg_.getChild("gRPCServer"); - std::string port = grpc_cfg.getAttrDefault("port", "50051"); + std::string port = cfg_.grpc_server().port(); std::string local_address = "0.0.0.0:" + port; auto camera_service = std::make_unique(); diff --git a/dependency/x86/third_party/soem/include/soem/ec_base.h b/dependency/x86/third_party/soem/include/soem/ec_base.h new file mode 100644 index 00000000..3eafa121 --- /dev/null +++ b/dependency/x86/third_party/soem/include/soem/ec_base.h @@ -0,0 +1,42 @@ +/* + * This software is dual-licensed under GPLv3 and a commercial + * license. See the file LICENSE.md distributed with this software for + * full license information. + */ + +/** \file + * \brief + * Headerfile for ec_base.c + */ + +#ifndef _ec_base_ +#define _ec_base_ + +#ifdef __cplusplus +extern "C" { +#endif + +int ecx_setupdatagram(ecx_portt *port, void *frame, uint8 com, uint8 idx, uint16 ADP, uint16 ADO, uint16 length, void *data); +uint16 ecx_adddatagram(ecx_portt *port, void *frame, uint8 com, uint8 idx, boolean more, uint16 ADP, uint16 ADO, uint16 length, void *data); +int ecx_BWR(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 length, void *data, int timeout); +int ecx_BRD(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 length, void *data, int timeout); +int ecx_APRD(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 length, void *data, int timeout); +int ecx_ARMW(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 length, void *data, int timeout); +int ecx_FRMW(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 length, void *data, int timeout); +uint16 ecx_APRDw(ecx_portt *port, uint16 ADP, uint16 ADO, int timeout); +int ecx_FPRD(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 length, void *data, int timeout); +uint16 ecx_FPRDw(ecx_portt *port, uint16 ADP, uint16 ADO, int timeout); +int ecx_APWRw(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 data, int timeout); +int ecx_APWR(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 length, void *data, int timeout); +int ecx_FPWRw(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 data, int timeout); +int ecx_FPWR(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 length, void *data, int timeout); +int ecx_LRW(ecx_portt *port, uint32 LogAdr, uint16 length, void *data, int timeout); +int ecx_LRD(ecx_portt *port, uint32 LogAdr, uint16 length, void *data, int timeout); +int ecx_LWR(ecx_portt *port, uint32 LogAdr, uint16 length, void *data, int timeout); +int ecx_LRWDC(ecx_portt *port, uint32 LogAdr, uint16 length, void *data, uint16 DCrs, int64 *DCtime, int timeout); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_coe.h b/dependency/x86/third_party/soem/include/soem/ec_coe.h new file mode 100644 index 00000000..45c889e5 --- /dev/null +++ b/dependency/x86/third_party/soem/include/soem/ec_coe.h @@ -0,0 +1,73 @@ +/* + * This software is dual-licensed under GPLv3 and a commercial + * license. See the file LICENSE.md distributed with this software for + * full license information. + */ + +/** \file + * \brief + * Headerfile for ec_coe.c + */ + +#ifndef _ec_coe_ +#define _ec_coe_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Storage for object description list */ +typedef struct +{ + /** slave number */ + uint16 Slave; + /** number of entries in list */ + uint16 Entries; + /** array of indexes */ + uint16 Index[EC_MAXODLIST]; + /** array of datatypes, see EtherCAT specification */ + uint16 DataType[EC_MAXODLIST]; + /** array of object codes, see EtherCAT specification */ + uint8 ObjectCode[EC_MAXODLIST]; + /** number of subindexes for each index */ + uint8 MaxSub[EC_MAXODLIST]; + /** textual description of each index */ + char Name[EC_MAXODLIST][EC_MAXNAME + 1]; +} ec_ODlistt; + +/* storage for object list entry information */ +typedef struct +{ + /** number of entries in list */ + uint16 Entries; + /** array of value infos, see EtherCAT specification */ + uint8 ValueInfo[EC_MAXOELIST]; + /** array of value infos, see EtherCAT specification */ + uint16 DataType[EC_MAXOELIST]; + /** array of bit lengths, see EtherCAT specification */ + uint16 BitLength[EC_MAXOELIST]; + /** array of object access bits, see EtherCAT specification */ + uint16 ObjAccess[EC_MAXOELIST]; + /** textual description of each index */ + char Name[EC_MAXOELIST][EC_MAXNAME + 1]; +} ec_OElistt; + +void ecx_SDOerror(ecx_contextt *context, uint16 Slave, uint16 Index, uint8 SubIdx, int32 AbortCode); +int ecx_SDOread(ecx_contextt *context, uint16 slave, uint16 index, uint8 subindex, + boolean CA, int *psize, void *p, int timeout); +int ecx_SDOwrite(ecx_contextt *context, uint16 Slave, uint16 Index, uint8 SubIndex, + boolean CA, int psize, const void *p, int Timeout); +int ecx_RxPDO(ecx_contextt *context, uint16 Slave, uint16 RxPDOnumber, int psize, const void *p); +int ecx_TxPDO(ecx_contextt *context, uint16 slave, uint16 TxPDOnumber, int *psize, void *p, int timeout); +int ecx_readPDOmap(ecx_contextt *context, uint16 Slave, uint32 *Osize, uint32 *Isize); +int ecx_readPDOmapCA(ecx_contextt *context, uint16 Slave, int Thread_n, uint32 *Osize, uint32 *Isize); +int ecx_readODlist(ecx_contextt *context, uint16 Slave, ec_ODlistt *pODlist); +int ecx_readODdescription(ecx_contextt *context, uint16 Item, ec_ODlistt *pODlist); +int ecx_readOEsingle(ecx_contextt *context, uint16 Item, uint8 SubI, ec_ODlistt *pODlist, ec_OElistt *pOElist); +int ecx_readOE(ecx_contextt *context, uint16 Item, ec_ODlistt *pODlist, ec_OElistt *pOElist); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_config.h b/dependency/x86/third_party/soem/include/soem/ec_config.h new file mode 100644 index 00000000..c0291177 --- /dev/null +++ b/dependency/x86/third_party/soem/include/soem/ec_config.h @@ -0,0 +1,31 @@ +/* + * This software is dual-licensed under GPLv3 and a commercial + * license. See the file LICENSE.md distributed with this software for + * full license information. + */ + +/** \file + * \brief + * Headerfile for ec_config.c + */ + +#ifndef _ec_config_ +#define _ec_config_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define EC_NODEOFFSET 0x1000 +#define EC_TEMPNODE 0xffff + +int ecx_config_init(ecx_contextt *context); +int ecx_config_map_group(ecx_contextt *context, void *pIOmap, uint8 group); +int ecx_recover_slave(ecx_contextt *context, uint16 slave, int timeout); +int ecx_reconfig_slave(ecx_contextt *context, uint16 slave, int timeout); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_dc.h b/dependency/x86/third_party/soem/include/soem/ec_dc.h new file mode 100644 index 00000000..d0472dd3 --- /dev/null +++ b/dependency/x86/third_party/soem/include/soem/ec_dc.h @@ -0,0 +1,27 @@ +/* + * This software is dual-licensed under GPLv3 and a commercial + * license. See the file LICENSE.md distributed with this software for + * full license information. + */ + +/** \file + * \brief + * Headerfile for ec_dc.c + */ + +#ifndef _EC_ECATDC_H +#define _EC_ECATDC_H + +#ifdef __cplusplus +extern "C" { +#endif + +boolean ecx_configdc(ecx_contextt *context); +void ecx_dcsync0(ecx_contextt *context, uint16 slave, boolean act, uint32 CyclTime, int32 CyclShift); +void ecx_dcsync01(ecx_contextt *context, uint16 slave, boolean act, uint32 CyclTime0, uint32 CyclTime1, int32 CyclShift); + +#ifdef __cplusplus +} +#endif + +#endif /* _EC_ECATDC_H */ diff --git a/dependency/x86/third_party/soem/include/soem/ec_eoe.h b/dependency/x86/third_party/soem/include/soem/ec_eoe.h new file mode 100644 index 00000000..27ca216e --- /dev/null +++ b/dependency/x86/third_party/soem/include/soem/ec_eoe.h @@ -0,0 +1,213 @@ +/* + * This software is dual-licensed under GPLv3 and a commercial + * license. See the file LICENSE.md distributed with this software for + * full license information. + */ + +/** \file + * \brief + * Headerfile for ec_eoe.c + */ + +#ifndef _ec_eoe_ +#define _ec_eoe_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "soem/soem.h" + +/* use maximum size for EOE mailbox data - mbxheader and 2x frameinfo */ +#define EC_MAXEOEDATA (EC_MAXMBX - (sizeof(ec_mbxheadert) + \ + sizeof(uint16_t) + \ + sizeof(uint16_t))) + +/** DNS length according to ETG 1000.6 */ +#define EOE_DNS_NAME_LENGTH 32 +/** Ethernet address length not including VLAN */ +#define EOE_ETHADDR_LENGTH 6 +/** IPv4 address length */ +#define EOE_IP4_LENGTH sizeof(uint32_t) + +#define EOE_MAKEU32(a, b, c, d) (((uint32_t)((a) & 0xff) << 24) | \ + ((uint32_t)((b) & 0xff) << 16) | \ + ((uint32_t)((c) & 0xff) << 8) | \ + (uint32_t)((d) & 0xff)) + +#if !defined(EC_BIG_ENDIAN) && defined(EC_LITTLE_ENDIAN) + +#define EOE_HTONS(x) ((((x) & 0x00ffUL) << 8) | (((x) & 0xff00UL) >> 8)) +#define EOE_NTOHS(x) EOE_HTONS(x) +#define EOE_HTONL(x) ((((x) & 0x000000ffUL) << 24) | \ + (((x) & 0x0000ff00UL) << 8) | \ + (((x) & 0x00ff0000UL) >> 8) | \ + (((x) & 0xff000000UL) >> 24)) +#define EOE_NTOHL(x) EOE_HTONL(x) +#else +#define EOE_HTONS(x) (x) +#define EOE_NTOHS(x) (x) +#define EOE_HTONL(x) (x) +#define EOE_NTOHL(x) (x) +#endif /* !defined(EC_BIG_ENDIAN) && defined(EC_LITTLE_ENDIAN) */ + +/** Get one byte from the 4-byte address */ +#define eoe_ip4_addr1(ipaddr) (((const uint8_t *)(&(ipaddr)->addr))[0]) +#define eoe_ip4_addr2(ipaddr) (((const uint8_t *)(&(ipaddr)->addr))[1]) +#define eoe_ip4_addr3(ipaddr) (((const uint8_t *)(&(ipaddr)->addr))[2]) +#define eoe_ip4_addr4(ipaddr) (((const uint8_t *)(&(ipaddr)->addr))[3]) + +/** Set an IP address given by the four byte-parts */ +#define EOE_IP4_ADDR_TO_U32(ipaddr, a, b, c, d) \ + (ipaddr)->addr = EOE_HTONL(EOE_MAKEU32(a, b, c, d)) + +/** Header frame info 1 */ +#define EOE_HDR_FRAME_TYPE_OFFSET 0 +#define EOE_HDR_FRAME_TYPE (0xF << 0) +#define EOE_HDR_FRAME_TYPE_SET(x) (((x) & 0xF) << 0) +#define EOE_HDR_FRAME_TYPE_GET(x) (((x) >> 0) & 0xF) +#define EOE_HDR_FRAME_PORT_OFFSET 4 +#define EOE_HDR_FRAME_PORT (0xF << 4) +#define EOE_HDR_FRAME_PORT_SET(x) ((uint16)(((x) & 0xF) << 4)) +#define EOE_HDR_FRAME_PORT_GET(x) (((x) >> 4) & 0xF) +#define EOE_HDR_LAST_FRAGMENT_OFFSET 8 +#define EOE_HDR_LAST_FRAGMENT (0x1 << 8) +#define EOE_HDR_LAST_FRAGMENT_SET(x) (((x) & 0x1) << 8) +#define EOE_HDR_LAST_FRAGMENT_GET(x) (((x) >> 8) & 0x1) +#define EOE_HDR_TIME_APPEND_OFFSET 9 +#define EOE_HDR_TIME_APPEND (0x1 << 9) +#define EOE_HDR_TIME_APPEND_SET(x) (((x) & 0x1) << 9) +#define EOE_HDR_TIME_APPEND_GET(x) (((x) >> 9) & 0x1) +#define EOE_HDR_TIME_REQUEST_OFFSET 10 +#define EOE_HDR_TIME_REQUEST (0x1 << 10) +#define EOE_HDR_TIME_REQUEST_SET(x) (((x) & 0x1) << 10) +#define EOE_HDR_TIME_REQUEST_GET(x) (((x) >> 10) & 0x1) + +/** Header frame info 2 */ +#define EOE_HDR_FRAG_NO_OFFSET 0 +#define EOE_HDR_FRAG_NO (0x3F << 0) +#define EOE_HDR_FRAG_NO_SET(x) ((uint16)(((x) & 0x3F) << 0)) +#define EOE_HDR_FRAG_NO_GET(x) (((x) >> 0) & 0x3F) +#define EOE_HDR_FRAME_OFFSET_OFFSET 6 +#define EOE_HDR_FRAME_OFFSET (0x3F << 6) +#define EOE_HDR_FRAME_OFFSET_SET(x) ((uint16)(((x) & 0x3F) << 6)) +#define EOE_HDR_FRAME_OFFSET_GET(x) (((x) >> 6) & 0x3F) +#define EOE_HDR_FRAME_NO_OFFSET 12 +#define EOE_HDR_FRAME_NO (0xF << 12) +#define EOE_HDR_FRAME_NO_SET(x) ((uint16)(((x) & 0xF) << 12)) +#define EOE_HDR_FRAME_NO_GET(x) (((x) >> 12) & 0xF) + +/** EOE param */ +#define EOE_PARAM_OFFSET 4 +#define EOE_PARAM_MAC_INCLUDE (0x1 << 0) +#define EOE_PARAM_IP_INCLUDE (0x1 << 1) +#define EOE_PARAM_SUBNET_IP_INCLUDE (0x1 << 2) +#define EOE_PARAM_DEFAULT_GATEWAY_INCLUDE (0x1 << 3) +#define EOE_PARAM_DNS_IP_INCLUDE (0x1 << 4) +#define EOE_PARAM_DNS_NAME_INCLUDE (0x1 << 5) + +/** EoE frame types */ +#define EOE_FRAG_DATA 0 +#define EOE_INIT_RESP_TIMESTAMP 1 +#define EOE_INIT_REQ 2 /* Spec SET IP REQ */ +#define EOE_INIT_RESP 3 /* Spec SET IP RESP */ +#define EOE_SET_ADDR_FILTER_REQ 4 +#define EOE_SET_ADDR_FILTER_RESP 5 +#define EOE_GET_IP_PARAM_REQ 6 +#define EOE_GET_IP_PARAM_RESP 7 +#define EOE_GET_ADDR_FILTER_REQ 8 +#define EOE_GET_ADDR_FILTER_RESP 9 + +/** EoE parameter result codes */ +#define EOE_RESULT_SUCCESS 0x0000 +#define EOE_RESULT_UNSPECIFIED_ERROR 0x0001 +#define EOE_RESULT_UNSUPPORTED_FRAME_TYPE 0x0002 +#define EOE_RESULT_NO_IP_SUPPORT 0x0201 +#define EOE_RESULT_NO_DHCP_SUPPORT 0x0202 +#define EOE_RESULT_NO_FILTER_SUPPORT 0x0401 + +/** EOE ip4 address in network order */ +typedef struct eoe_ip4_addr +{ + uint32_t addr; +} eoe_ip4_addr_t; + +/** EOE ethernet address */ +OSAL_PACKED_BEGIN +typedef struct OSAL_PACKED eoe_ethaddr +{ + uint8_t addr[EOE_ETHADDR_LENGTH]; +} eoe_ethaddr_t; +OSAL_PACKED_END + +/** EoE IP request structure, storage only, no need to pack */ +typedef struct eoe_param +{ + uint8_t mac_set : 1; + uint8_t ip_set : 1; + uint8_t subnet_set : 1; + uint8_t default_gateway_set : 1; + uint8_t dns_ip_set : 1; + uint8_t dns_name_set : 1; + eoe_ethaddr_t mac; + eoe_ip4_addr_t ip; + eoe_ip4_addr_t subnet; + eoe_ip4_addr_t default_gateway; + eoe_ip4_addr_t dns_ip; + char dns_name[EOE_DNS_NAME_LENGTH]; +} eoe_param_t; + +/** EOE structure. + * Used to interpret EoE mailbox packets. + */ +OSAL_PACKED_BEGIN +typedef struct OSAL_PACKED +{ + ec_mbxheadert mbxheader; + uint16_t frameinfo1; + union + { + uint16_t frameinfo2; + uint16_t result; + }; + uint8 data[EC_MAXEOEDATA]; +} ec_EOEt; +OSAL_PACKED_END + +int ecx_EOEdefinehook(ecx_contextt *context, void *hook); +int ecx_EOEsetIp(ecx_contextt *context, + uint16 slave, + uint8 port, + eoe_param_t *ipparam, + int timeout); +int ecx_EOEgetIp(ecx_contextt *context, + uint16 slave, + uint8 port, + eoe_param_t *ipparam, + int timeout); +int ecx_EOEsend(ecx_contextt *context, + uint16 slave, + uint8 port, + int psize, + void *p, + int timeout); +int ecx_EOErecv(ecx_contextt *context, + uint16 slave, + uint8 port, + int *psize, + void *p, + int timeout); +int ecx_EOEreadfragment( + ec_mbxbuft *MbxIn, + uint8 *rxfragmentno, + uint16 *rxframesize, + uint16 *rxframeoffset, + uint16 *rxframeno, + int *psize, + void *p); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_foe.h b/dependency/x86/third_party/soem/include/soem/ec_foe.h new file mode 100644 index 00000000..6348b08a --- /dev/null +++ b/dependency/x86/third_party/soem/include/soem/ec_foe.h @@ -0,0 +1,27 @@ +/* + * This software is dual-licensed under GPLv3 and a commercial + * license. See the file LICENSE.md distributed with this software for + * full license information. + */ + +/** \file + * \brief + * Headerfile for ec_foe.c + */ + +#ifndef _ec_foe_ +#define _ec_foe_ + +#ifdef __cplusplus +extern "C" { +#endif + +int ecx_FOEdefinehook(ecx_contextt *context, void *hook); +int ecx_FOEread(ecx_contextt *context, uint16 slave, char *filename, uint32 password, int *psize, void *p, int timeout); +int ecx_FOEwrite(ecx_contextt *context, uint16 slave, char *filename, uint32 password, int psize, void *p, int timeout); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_main.h b/dependency/x86/third_party/soem/include/soem/ec_main.h new file mode 100644 index 00000000..70189802 --- /dev/null +++ b/dependency/x86/third_party/soem/include/soem/ec_main.h @@ -0,0 +1,624 @@ +/* + * This software is dual-licensed under GPLv3 and a commercial + * license. See the file LICENSE.md distributed with this software for + * full license information. + */ + +/** \file + * \brief + * Headerfile for ec_main.c + */ + +#ifndef _ec_main_ +#define _ec_main_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "soem/ec_options.h" + +typedef struct ec_adapter ec_adaptert; +struct ec_adapter +{ + char name[EC_MAXLEN_ADAPTERNAME]; + char desc[EC_MAXLEN_ADAPTERNAME]; + ec_adaptert *next; +}; + +/** record for FMMU */ +OSAL_PACKED_BEGIN +typedef struct OSAL_PACKED ec_fmmu +{ + uint32 LogStart; + uint16 LogLength; + uint8 LogStartbit; + uint8 LogEndbit; + uint16 PhysStart; + uint8 PhysStartBit; + uint8 FMMUtype; + uint8 FMMUactive; + uint8 unused1; + uint16 unused2; +} ec_fmmut; +OSAL_PACKED_END + +/** record for sync manager */ +OSAL_PACKED_BEGIN +typedef struct OSAL_PACKED ec_sm +{ + uint16 StartAddr; + uint16 SMlength; + uint32 SMflags; +} ec_smt; +OSAL_PACKED_END + +OSAL_PACKED_BEGIN +typedef struct OSAL_PACKED ec_state_status +{ + uint16 State; + uint16 Unused; + uint16 ALstatuscode; +} ec_state_status; +OSAL_PACKED_END + +/** mailbox buffer array */ +typedef uint8 ec_mbxbuft[EC_MAXMBX + 1]; + +#define EC_MBXINENABLE (uint8 *)1 + +typedef struct +{ + int listhead, listtail, listcount; + int mbxemptylist[EC_MBXPOOLSIZE]; + osal_mutext *mbxmutex; + ec_mbxbuft mbx[EC_MBXPOOLSIZE]; +} ec_mbxpoolt; + +#define EC_MBXQUEUESTATE_NONE 0 +#define EC_MBXQUEUESTATE_REQ 1 +#define EC_MBXQUEUESTATE_FAIL 2 +#define EC_MBXQUEUESTATE_DONE 3 + +typedef struct +{ + int listhead, listtail, listcount; + ec_mbxbuft *mbx[EC_MBXPOOLSIZE]; + int mbxstate[EC_MBXPOOLSIZE]; + int mbxremove[EC_MBXPOOLSIZE]; + int mbxticket[EC_MBXPOOLSIZE]; + uint16 mbxslave[EC_MBXPOOLSIZE]; + osal_mutext *mbxmutex; +} ec_mbxqueuet; + +#define ECT_MBXPROT_AOE 0x0001 +#define ECT_MBXPROT_EOE 0x0002 +#define ECT_MBXPROT_COE 0x0004 +#define ECT_MBXPROT_FOE 0x0008 +#define ECT_MBXPROT_SOE 0x0010 +#define ECT_MBXPROT_VOE 0x0020 + +#define ECT_COEDET_SDO 0x01 +#define ECT_COEDET_SDOINFO 0x02 +#define ECT_COEDET_PDOASSIGN 0x04 +#define ECT_COEDET_PDOCONFIG 0x08 +#define ECT_COEDET_UPLOAD 0x10 +#define ECT_COEDET_SDOCA 0x20 + +#define EC_SMENABLEMASK 0xfffeffff + +typedef struct ecx_context ecx_contextt; + +#define ECT_MBXH_NONE 0 +#define ECT_MBXH_CYCLIC 1 +#define ECT_MBXH_LOST 2 + +/** Slave state + * All slave information is put in this structure. Needed for most + * user interaction with slaves. + */ +typedef struct ec_slave +{ + /** state of slave */ + uint16 state; + /** AL status code */ + uint16 ALstatuscode; + /** Configured address */ + uint16 configadr; + /** Alias address */ + uint16 aliasadr; + /** Manufacturer from EEprom */ + uint32 eep_man; + /** ID from EEprom */ + uint32 eep_id; + /** revision from EEprom */ + uint32 eep_rev; + /** serial number from EEprom */ + uint32 eep_ser; + /** Interface type */ + uint16 Itype; + /** Device type */ + uint16 Dtype; + /** output bits */ + uint16 Obits; + /** output bytes, if Obits < 8 then Obytes = 0 */ + uint32 Obytes; + /** output pointer in IOmap buffer */ + uint8 *outputs; + /** output offset in IOmap buffer */ + uint32 Ooffset; + /** startbit in first output byte */ + uint8 Ostartbit; + /** input bits */ + uint16 Ibits; + /** input bytes, if Ibits < 8 then Ibytes = 0 */ + uint32 Ibytes; + /** input pointer in IOmap buffer */ + uint8 *inputs; + /** input offset in IOmap buffer */ + uint32 Ioffset; + /** startbit in first input byte */ + uint8 Istartbit; + /** SM structure */ + ec_smt SM[EC_MAXSM]; + /** SM type 0=unused 1=MbxWr 2=MbxRd 3=Outputs 4=Inputs */ + uint8 SMtype[EC_MAXSM]; + /** FMMU structure */ + ec_fmmut FMMU[EC_MAXFMMU]; + /** FMMU0 function 0=unused 1=outputs 2=inputs 3=SM status*/ + uint8 FMMU0func; + /** FMMU1 function */ + uint8 FMMU1func; + /** FMMU2 function */ + uint8 FMMU2func; + /** FMMU3 function */ + uint8 FMMU3func; + /** length of write mailbox in bytes, if no mailbox then 0 */ + uint16 mbx_l; + /** mailbox write offset */ + uint16 mbx_wo; + /** length of read mailbox in bytes */ + uint16 mbx_rl; + /** mailbox read offset */ + uint16 mbx_ro; + /** mailbox supported protocols */ + uint16 mbx_proto; + /** Counter value of mailbox link layer protocol 1..7 */ + uint8 mbx_cnt; + /** has DC capability */ + boolean hasdc; + /** Physical type; Ebus, EtherNet combinations */ + uint8 ptype; + /** topology: 1 to 3 links */ + uint8 topology; + /** active ports bitmap : ....3210 , set if respective port is active **/ + uint8 activeports; + /** consumed ports bitmap : ....3210, used for internal delay measurement **/ + uint8 consumedports; + /** slave number for parent, 0=master */ + uint16 parent; + /** port number on parent this slave is connected to **/ + uint8 parentport; + /** port number on this slave the parent is connected to **/ + uint8 entryport; + /** DC receivetimes on port A */ + int32 DCrtA; + /** DC receivetimes on port B */ + int32 DCrtB; + /** DC receivetimes on port C */ + int32 DCrtC; + /** DC receivetimes on port D */ + int32 DCrtD; + /** propagation delay */ + int32 pdelay; + /** next DC slave */ + uint16 DCnext; + /** previous DC slave */ + uint16 DCprevious; + /** DC cycle time in ns */ + int32 DCcycle; + /** DC shift from clock modulus boundary */ + int32 DCshift; + /** DC sync activation, 0=off, 1=on */ + uint8 DCactive; + /** link to SII config */ + uint16 SIIindex; + /** 1 = 8 bytes per read, 0 = 4 bytes per read */ + uint8 eep_8byte; + /** 0 = eeprom to master , 1 = eeprom to PDI */ + uint8 eep_pdi; + /** CoE details */ + uint8 CoEdetails; + /** FoE details */ + uint8 FoEdetails; + /** EoE details */ + uint8 EoEdetails; + /** SoE details */ + uint8 SoEdetails; + /** E-bus current */ + int16 Ebuscurrent; + /** if >0 block use of LRW in processdata */ + uint8 blockLRW; + /** group */ + uint8 group; + /** first unused FMMU */ + uint8 FMMUunused; + /** Boolean for tracking whether the slave is (not) responding, not used/set by the SOEM library */ + boolean islost; + /** registered configuration function PO->SO */ + int (*PO2SOconfig)(ecx_contextt *context, uint16 slave); + /** mailbox handler state, 0 = no handler, 1 = cyclic task mbx handler, 2 = slave lost */ + int mbxhandlerstate; + /** mailbox handler robust mailbox protocol state */ + int mbxrmpstate; + /** mailbox handler RMP extended mbx in state */ + uint16 mbxinstateex; + /** pointer to CoE mailbox in buffer */ + uint8 *coembxin; + /** CoE mailbox in flag, true = mailbox full */ + boolean coembxinfull; + /** CoE mailbox in overrun counter */ + int coembxoverrun; + /** pointer to SoE mailbox in buffer */ + uint8 *soembxin; + /** SoE mailbox in flag, true = mailbox full */ + boolean soembxinfull; + /** SoE mailbox in overrun counter */ + int soembxoverrun; + /** pointer to FoE mailbox in buffer */ + uint8 *foembxin; + /** FoE mailbox in flag, true = mailbox full */ + boolean foembxinfull; + /** FoE mailbox in overrun counter */ + int foembxoverrun; + /** pointer to EoE mailbox in buffer */ + uint8 *eoembxin; + /** EoE mailbox in flag, true = mailbox full */ + boolean eoembxinfull; + /** EoE mailbox in overrun counter */ + int eoembxoverrun; + /** pointer to VoE mailbox in buffer */ + uint8 *voembxin; + /** VoE mailbox in flag, true = mailbox full */ + boolean voembxinfull; + /** VoE mailbox in overrun counter */ + int voembxoverrun; + /** pointer to AoE mailbox in buffer */ + uint8 *aoembxin; + /** AoE mailbox in flag, true = mailbox full */ + boolean aoembxinfull; + /** AoE mailbox in overrun counter */ + int aoembxoverrun; + /** pointer to out mailbox status register buffer */ + uint8 *mbxstatus; + /** readable name */ + char name[EC_MAXNAME + 1]; +} ec_slavet; + +/** for list of ethercat slave groups */ +typedef struct ec_group +{ + /** logical start address for this group */ + uint32 logstartaddr; + /** output bytes, if Obits < 8 then Obytes = 0 */ + uint32 Obytes; + /** output pointer in IOmap buffer */ + uint8 *outputs; + /** input bytes, if Ibits < 8 then Ibytes = 0 */ + uint32 Ibytes; + /** input pointer in IOmap buffer */ + uint8 *inputs; + /** has DC capability */ + boolean hasdc; + /** next DC slave */ + uint16 DCnext; + /** E-bus current */ + int16 Ebuscurrent; + /** if >0 block use of LRW in processdata */ + uint8 blockLRW; + /** IO segments used */ + uint16 nsegments; + /** 1st input segment */ + uint16 Isegment; + /** Offset in input segment */ + uint16 Ioffset; + /** Expected workcounter outputs */ + uint16 outputsWKC; + /** Expected workcounter inputs */ + uint16 inputsWKC; + /** check slave states */ + boolean docheckstate; + /** IO segmentation list. Datagrams must not break SM in two. */ + uint32 IOsegment[EC_MAXIOSEGMENTS]; + /** pointer to out mailbox status register buffer */ + uint8 *mbxstatus; + /** mailbox status register buffer length */ + int32 mbxstatuslength; + /** mailbox status lookup table */ + uint16 mbxstatuslookup[EC_MAXSLAVE]; + /** mailbox last handled in mxbhandler */ + uint16 lastmbxpos; + /** mailbox transmit queue struct */ + ec_mbxqueuet mbxtxqueue; +} ec_groupt; + +#define ECT_ESMTRANS_IP 0x0001 +#define ECT_ESMTRANS_PS 0x0002 +#define ECT_ESMTRANS_PI 0x0004 +#define ECT_ESMTRANS_SP 0x0008 +#define ECT_ESMTRANS_SO 0x0010 +#define ECT_ESMTRANS_SI 0x0020 +#define ECT_ESMTRANS_OS 0x0040 +#define ECT_ESMTRANS_OP 0x0080 +#define ECT_ESMTRANS_OI 0x0100 +#define ECT_ESMTRANS_IB 0x0200 +#define ECT_ESMTRANS_BI 0x0400 +#define ECT_ESMTRANS_II 0x0800 +#define ECT_ESMTRANS_PP 0x1000 +#define ECT_ESMTRANS_SS 0x2000 + +/** ENI CoE command structure */ +typedef struct ec_enicoecmd +{ + /** transition(s) during which command should be sent */ + uint16 Transition; + /** complete access flag */ + boolean CA; + /** ccs (1 = read, 2 = write) */ + uint8 Ccs; + /** object index */ + uint16 Index; + /** object subindex */ + uint8 SubIdx; + /** timeout in us */ + int Timeout; + /** size in bytes of parameter buffer */ + int DataSize; + /** pointer to parameter buffer */ + void *Data; +} ec_enicoecmdt; + +/** ENI slave structure */ +typedef struct ec_enislave +{ + uint16 Slave; + uint32 VendorId; + uint32 ProductCode; + uint32 RevisionNo; + ec_enicoecmdt *CoECmds; + int CoECmdCount; +} ec_enislavet; + +/** ENI structure */ +typedef struct ec_eni +{ + ec_enislavet *slave; + int slavecount; +} ec_enit; + +/** SII FMMU structure */ +typedef struct ec_eepromFMMU +{ + uint16 Startpos; + uint8 nFMMU; + uint8 FMMU0; + uint8 FMMU1; + uint8 FMMU2; + uint8 FMMU3; +} ec_eepromFMMUt; + +/** SII SM structure */ +typedef struct ec_eepromSM +{ + uint16 Startpos; + uint8 nSM; + uint16 PhStart; + uint16 Plength; + uint8 Creg; + uint8 Sreg; /* don't care */ + uint8 Activate; + uint8 PDIctrl; /* don't care */ +} ec_eepromSMt; + +/** record to store rxPDO and txPDO table from eeprom */ +typedef struct ec_eepromPDO +{ + uint16 Startpos; + uint16 Length; + uint16 nPDO; + uint16 Index[EC_MAXEEPDO]; + uint16 SyncM[EC_MAXEEPDO]; + uint16 BitSize[EC_MAXEEPDO]; + uint16 SMbitsize[EC_MAXSM]; +} ec_eepromPDOt; + +/** standard ethercat mailbox header */ +OSAL_PACKED_BEGIN +typedef struct OSAL_PACKED ec_mbxheader +{ + uint16 length; + uint16 address; + uint8 priority; + uint8 mbxtype; +} ec_mbxheadert; +OSAL_PACKED_END + +/** ALstatus and ALstatus code */ +OSAL_PACKED_BEGIN +typedef struct OSAL_PACKED ec_alstatus +{ + uint16 alstatus; + uint16 unused; + uint16 alstatuscode; +} ec_alstatust; +OSAL_PACKED_END + +/** stack structure to store segmented LRD/LWR/LRW constructs */ +typedef struct ec_idxstack +{ + uint8 pushed; + uint8 pulled; + uint8 idx[EC_MAXBUF]; + void *data[EC_MAXBUF]; + uint16 length[EC_MAXBUF]; + uint16 dcoffset[EC_MAXBUF]; + uint8 type[EC_MAXBUF]; +} ec_idxstackT; + +/** ringbuf for error storage */ +typedef struct ec_ering +{ + int16 head; + int16 tail; + ec_errort Error[EC_MAXELIST + 1]; +} ec_eringt; + +/** SyncManager Communication Type structure for CA */ +OSAL_PACKED_BEGIN +typedef struct OSAL_PACKED ec_SMcommtype +{ + uint8 n; + uint8 nu1; + uint8 SMtype[EC_MAXSM]; +} ec_SMcommtypet; +OSAL_PACKED_END + +/** SDO assign structure for CA */ +OSAL_PACKED_BEGIN +typedef struct OSAL_PACKED ec_PDOassign +{ + uint8 n; + uint8 nu1; + uint16 index[256]; +} ec_PDOassignt; +OSAL_PACKED_END + +/** SDO description structure for CA */ +OSAL_PACKED_BEGIN +typedef struct OSAL_PACKED ec_PDOdesc +{ + uint8 n; + uint8 nu1; + uint32 PDO[256]; +} ec_PDOdesct; +OSAL_PACKED_END + +/** Context structure, referenced by all ecx functions*/ +struct ecx_context +{ + /** @publicsection */ + /* Network state */ + + /** port, may include red_port */ + ecx_portt port; + /** list of detected slaves */ + ec_slavet slavelist[EC_MAXSLAVE]; + /** number of slaves found in configuration */ + int slavecount; + /** list of groups */ + ec_groupt grouplist[EC_MAXGROUP]; + /** ecaterror state */ + boolean ecaterror; + /** last DC time from slaves */ + int64 DCtime; + + /** @privatesection */ + /* Internal state */ + + /** internal, eeprom cache buffer */ + uint8 esibuf[EC_MAXEEPBUF]; + /** internal, eeprom cache map */ + uint32 esimap[EC_MAXEEPBITMAP]; + /** internal, current slave for eeprom cache */ + uint16 esislave; + /** internal, error list */ + ec_eringt elist; + /** internal, processdata stack buffer info */ + ec_idxstackT idxstack; + /** internal, SM buffer */ + ec_SMcommtypet SMcommtype[EC_MAX_MAPT]; + /** internal, PDO assign list */ + ec_PDOassignt PDOassign[EC_MAX_MAPT]; + /** internal, PDO description list */ + ec_PDOdesct PDOdesc[EC_MAX_MAPT]; + /** internal, SM list from eeprom */ + ec_eepromSMt eepSM; + /** internal, FMMU list from eeprom */ + ec_eepromFMMUt eepFMMU; + /** internal, mailbox pool */ + ec_mbxpoolt mbxpool; + + /** @publicsection */ + /* Configurable settings */ + + /** network information hook */ + ec_enit *ENI; + /** registered FoE hook */ + int (*FOEhook)(uint16 slave, int packetnumber, int datasize); + /** registered EoE hook */ + int (*EOEhook)(ecx_contextt *context, uint16 slave, void *eoembx); + /** flag to control legacy automatic state change or manual state change */ + int manualstatechange; + /** opaque pointer to application userdata, never used by SOEM. */ + void *userdata; + /** In overlapped mode, inputs will replace outputs in the incoming + * frame. Use this mode for TI ESC:s. Processdata is always aligned + * on a byte boundary. */ + boolean overlappedMode; + /** Do not map each slave on a byte boundary. May result in smaller + * frame sizes. Has no effect in overlapped mode. */ + boolean packedMode; +}; + +ec_adaptert *ec_find_adapters(void); +void ec_free_adapters(ec_adaptert *adapter); +uint8 ec_nextmbxcnt(uint8 cnt); +void ec_clearmbx(ec_mbxbuft *Mbx); +void ecx_pusherror(ecx_contextt *context, const ec_errort *Ec); +boolean ecx_poperror(ecx_contextt *context, ec_errort *Ec); +boolean ecx_iserror(ecx_contextt *context); +void ecx_packeterror(ecx_contextt *context, uint16 Slave, uint16 Index, uint8 SubIdx, uint16 ErrorCode); +int ecx_init(ecx_contextt *context, const char *ifname); +int ecx_init_redundant(ecx_contextt *context, ecx_redportt *redport, const char *ifname, char *if2name); +void ecx_close(ecx_contextt *context); +uint8 ecx_siigetbyte(ecx_contextt *context, uint16 slave, uint16 address); +int16 ecx_siifind(ecx_contextt *context, uint16 slave, uint16 cat); +void ecx_siistring(ecx_contextt *context, char *str, uint16 slave, uint16 Sn); +uint16 ecx_siiFMMU(ecx_contextt *context, uint16 slave, ec_eepromFMMUt *FMMU); +uint16 ecx_siiSM(ecx_contextt *context, uint16 slave, ec_eepromSMt *SM); +uint16 ecx_siiSMnext(ecx_contextt *context, uint16 slave, ec_eepromSMt *SM, uint16 n); +uint32 ecx_siiPDO(ecx_contextt *context, uint16 slave, ec_eepromPDOt *PDO, uint8 t); +int ecx_readstate(ecx_contextt *context); +int ecx_writestate(ecx_contextt *context, uint16 slave); +uint16 ecx_statecheck(ecx_contextt *context, uint16 slave, uint16 reqstate, int timeout); +int ecx_mbxhandler(ecx_contextt *context, uint8 group, int limit); +int ecx_mbxempty(ecx_contextt *context, uint16 slave, int timeout); +int ecx_mbxsend(ecx_contextt *context, uint16 slave, ec_mbxbuft *mbx, int timeout); +int ecx_mbxreceive(ecx_contextt *context, uint16 slave, ec_mbxbuft **mbx, int timeout); +int ecx_mbxENIinitcmds(ecx_contextt *context, uint16 slave, uint16_t transition); +void ecx_esidump(ecx_contextt *context, uint16 slave, uint8 *esibuf); +uint32 ecx_readeeprom(ecx_contextt *context, uint16 slave, uint16 eeproma, int timeout); +int ecx_writeeeprom(ecx_contextt *context, uint16 slave, uint16 eeproma, uint16 data, int timeout); +int ecx_eeprom2master(ecx_contextt *context, uint16 slave); +int ecx_eeprom2pdi(ecx_contextt *context, uint16 slave); +uint64 ecx_readeepromAP(ecx_contextt *context, uint16 aiadr, uint16 eeproma, int timeout); +int ecx_writeeepromAP(ecx_contextt *context, uint16 aiadr, uint16 eeproma, uint16 data, int timeout); +uint64 ecx_readeepromFP(ecx_contextt *context, uint16 configadr, uint16 eeproma, int timeout); +int ecx_writeeepromFP(ecx_contextt *context, uint16 configadr, uint16 eeproma, uint16 data, int timeout); +void ecx_readeeprom1(ecx_contextt *context, uint16 slave, uint16 eeproma); +uint32 ecx_readeeprom2(ecx_contextt *context, uint16 slave, int timeout); +int ecx_receive_processdata_group(ecx_contextt *context, uint8 group, int timeout); +int ecx_send_processdata(ecx_contextt *context); +int ecx_receive_processdata(ecx_contextt *context, int timeout); +int ecx_send_processdata_group(ecx_contextt *context, uint8 group); +ec_mbxbuft *ecx_getmbx(ecx_contextt *context); +int ecx_dropmbx(ecx_contextt *context, ec_mbxbuft *mbx); +int ecx_initmbxpool(ecx_contextt *context); +int ecx_initmbxqueue(ecx_contextt *context, uint8 group); +int ecx_slavembxcyclic(ecx_contextt *context, uint16 slave); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_options.h b/dependency/x86/third_party/soem/include/soem/ec_options.h new file mode 100644 index 00000000..a6b3d7f9 --- /dev/null +++ b/dependency/x86/third_party/soem/include/soem/ec_options.h @@ -0,0 +1,128 @@ +/* + * This software is dual-licensed under GPLv3 and a commercial + * license. See the file LICENSE.md distributed with this software for + * full license information. + */ + +/** \file + * \brief + * Build options for SOEM + */ + +#ifndef _ec_options_ +#define _ec_options_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Max sizes */ + +/** standard frame buffer size in bytes */ +#define EC_BUFSIZE (EC_MAXECATFRAME) + +/** number of frame buffers per channel (tx, rx1 rx2) */ +#define EC_MAXBUF (16) + +/** size of EEPROM bitmap cache */ +#define EC_MAXEEPBITMAP (128) + +/** size of EEPROM cache buffer */ +#define EC_MAXEEPBUF (EC_MAXEEPBITMAP << 5) + +/** default group size in 2^x */ +#define EC_LOGGROUPOFFSET (16) + +/** max. entries in EtherCAT error list */ +#define EC_MAXELIST (64) + +/** max. length of readable name in slavelist and Object Description List */ +#define EC_MAXNAME (40) + +/** max. number of slaves in array */ +#define EC_MAXSLAVE (200) + +/** max. number of groups */ +#define EC_MAXGROUP (2) + +/** max. number of IO segments per group */ +#define EC_MAXIOSEGMENTS (64) + +/** max. mailbox size */ +#define EC_MAXMBX (1486) + +/** number of mailboxes in pool */ +#define EC_MBXPOOLSIZE (32) + +/** max. eeprom PDO entries */ +#define EC_MAXEEPDO (0x200) + +/** max. SM used */ +#define EC_MAXSM (8) + +/** max. FMMU used */ +#define EC_MAXFMMU (4) + +/** max. adapter name length */ +#define EC_MAXLEN_ADAPTERNAME (128) + +/** define maximum number of concurrent threads in mapping */ +#define EC_MAX_MAPT (1) + +/** max entries in Object Description list */ +#define EC_MAXODLIST (1024) + +/** max entries in Object Entry list */ +#define EC_MAXOELIST (256) + +/** max. length of readable SoE name */ +#define EC_SOE_MAXNAME (60) + +/** max. number of SoE mappings */ +#define EC_SOE_MAXMAPPING (64) + +/* Timeouts and retries */ + +/** timeout value in us for tx frame to return to rx */ +#define EC_TIMEOUTRET (2000) + +/** timeout value in us for safe data transfer, max. triple retry */ +#define EC_TIMEOUTRET3 (EC_TIMEOUTRET * 3) + +/** timeout value in us for return "safe" variant (f.e. wireless) */ +#define EC_TIMEOUTSAFE (20000) + +/** timeout value in us for EEPROM access */ +#define EC_TIMEOUTEEP (20000) + +/** timeout value in us for tx mailbox cycle */ +#define EC_TIMEOUTTXM (20000) + +/** timeout value in us for rx mailbox cycle */ +#define EC_TIMEOUTRXM (700000) + +/** timeout value in us for check statechange */ +#define EC_TIMEOUTSTATE (2000000) + +/** default number of retries if wkc <= 0 */ +#define EC_DEFAULTRETRIES (3) + +/* MAC addresses */ + +/** Primary source MAC address used for EtherCAT. + * + * This address is not the MAC address used from the NIC. EtherCAT + * does not care about MAC addressing, but it is used here to + * differentiate the route the packet traverses through the EtherCAT + * segment. This is needed to find out the packet flow in redundant + * configurations. */ +#define EC_PRIMARY_MAC_ARRAY {0x0101, 0x0101, 0x0101} + +/** Secondary source MAC address used for EtherCAT. */ +#define EC_SECONDARY_MAC_ARRAY {0x0404, 0x0404, 0x0404} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_print.h b/dependency/x86/third_party/soem/include/soem/ec_print.h new file mode 100644 index 00000000..920f3dac --- /dev/null +++ b/dependency/x86/third_party/soem/include/soem/ec_print.h @@ -0,0 +1,30 @@ +/* + * This software is dual-licensed under GPLv3 and a commercial + * license. See the file LICENSE.md distributed with this software for + * full license information. + */ + +/** \file + * \brief + * Headerfile for ec_print.c + */ + +#ifndef _ec_print_ +#define _ec_print_ + +#ifdef __cplusplus +extern "C" { +#endif + +const char *ec_sdoerror2string(uint32 sdoerrorcode); +char *ec_ALstatuscode2string(uint16 ALstatuscode); +char *ec_soeerror2string(uint16 errorcode); +char *ec_mbxerror2string(uint16 errorcode); +char *ecx_err2string(const ec_errort Ec); +char *ecx_elist2string(ecx_contextt *context); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_soe.h b/dependency/x86/third_party/soem/include/soem/ec_soe.h new file mode 100644 index 00000000..e6dbf3ee --- /dev/null +++ b/dependency/x86/third_party/soem/include/soem/ec_soe.h @@ -0,0 +1,121 @@ +/* + * This software is dual-licensed under GPLv3 and a commercial + * license. See the file LICENSE.md distributed with this software for + * full license information. + */ + +/** \file + * \brief + * Headerfile for ec_soe.c + */ + +#ifndef _ec_soe_ +#define _ec_soe_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define EC_SOE_DATASTATE_B 0x01 +#define EC_SOE_NAME_B 0x02 +#define EC_SOE_ATTRIBUTE_B 0x04 +#define EC_SOE_UNIT_B 0x08 +#define EC_SOE_MIN_B 0x10 +#define EC_SOE_MAX_B 0x20 +#define EC_SOE_VALUE_B 0x40 +#define EC_SOE_DEFAULT_B 0x80 + +#define EC_IDN_MDTCONFIG 24 +#define EC_IDN_ATCONFIG 16 + +/** SoE name structure */ +OSAL_PACKED_BEGIN +typedef struct OSAL_PACKED +{ + /** current length in bytes of list */ + uint16 currentlength; + /** maximum length in bytes of list */ + uint16 maxlength; + char name[EC_SOE_MAXNAME]; +} ec_SoEnamet; +OSAL_PACKED_END + +/** SoE list structure */ +OSAL_PACKED_BEGIN +typedef struct OSAL_PACKED +{ + /** current length in bytes of list */ + uint16 currentlength; + /** maximum length in bytes of list */ + uint16 maxlength; + union + { + uint8 byte[8]; + uint16 word[4]; + uint32 dword[2]; + uint64 lword[1]; + }; +} ec_SoElistt; +OSAL_PACKED_END + +/** SoE IDN mapping structure */ +OSAL_PACKED_BEGIN +typedef struct OSAL_PACKED +{ + /** current length in bytes of list */ + uint16 currentlength; + /** maximum length in bytes of list */ + uint16 maxlength; + uint16 idn[EC_SOE_MAXMAPPING]; +} ec_SoEmappingt; +OSAL_PACKED_END + +#define EC_SOE_LENGTH_1 0x00 +#define EC_SOE_LENGTH_2 0x01 +#define EC_SOE_LENGTH_4 0x02 +#define EC_SOE_LENGTH_8 0x03 +#define EC_SOE_TYPE_BINARY 0x00 +#define EC_SOE_TYPE_UINT 0x01 +#define EC_SOE_TYPE_INT 0x02 +#define EC_SOE_TYPE_HEX 0x03 +#define EC_SOE_TYPE_STRING 0x04 +#define EC_SOE_TYPE_IDN 0x05 +#define EC_SOE_TYPE_FLOAT 0x06 +#define EC_SOE_TYPE_PARAMETER 0x07 + +/** SoE attribute structure */ +OSAL_PACKED_BEGIN +typedef struct OSAL_PACKED +{ + /** evaluation factor for display purposes */ + uint32 evafactor : 16; + /** length of IDN element(s) */ + uint32 length : 2; + /** IDN is list */ + uint32 list : 1; + /** IDN is command */ + uint32 command : 1; + /** datatype */ + uint32 datatype : 3; + uint32 reserved1 : 1; + /** decimals to display if float datatype */ + uint32 decimals : 4; + /** write protected in pre-op */ + uint32 wppreop : 1; + /** write protected in safe-op */ + uint32 wpsafeop : 1; + /** write protected in op */ + uint32 wpop : 1; + uint32 reserved2 : 1; +} ec_SoEattributet; +OSAL_PACKED_END + +int ecx_SoEread(ecx_contextt *context, uint16 slave, uint8 driveNo, uint8 elementflags, uint16 idn, int *psize, void *p, int timeout); +int ecx_SoEwrite(ecx_contextt *context, uint16 slave, uint8 driveNo, uint8 elementflags, uint16 idn, int psize, void *p, int timeout); +int ecx_readIDNmap(ecx_contextt *context, uint16 slave, uint32 *Osize, uint32 *Isize); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_type.h b/dependency/x86/third_party/soem/include/soem/ec_type.h new file mode 100644 index 00000000..faca1707 --- /dev/null +++ b/dependency/x86/third_party/soem/include/soem/ec_type.h @@ -0,0 +1,551 @@ +/* + * This software is dual-licensed under GPLv3 and a commercial + * license. See the file LICENSE.md distributed with this software for + * full license information. + */ + +/** \file + * \brief + * General typedefs and defines for EtherCAT. + * + * Defines that could need optimisation for specific applications + * are the EC_TIMEOUTxxx. Assumptions for the standard settings are a + * standard linux PC or laptop and a wired connection to maximal 100 slaves. + * For use with wireless connections or lots of slaves the timeouts need + * increasing. For fast systems running Xenomai and RT-net or alike the + * timeouts need to be shorter. + */ + +#ifndef _EC_TYPE_H +#define _EC_TYPE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "osal.h" +#include "soem/ec_options.h" + +/* Endianness */ + +/** Define little endian target by default if no endian is set */ +#if !defined(EC_LITTLE_ENDIAN) && !defined(EC_BIG_ENDIAN) +#define EC_LITTLE_ENDIAN +#endif + +/* Error codes */ + +/** return value no frame returned */ +#define EC_NOFRAME -1 +/** return value unknown frame received */ +#define EC_OTHERFRAME -2 +/** return value general error */ +#define EC_ERROR -3 +/** return value too many slaves */ +#define EC_SLAVECOUNTEXCEEDED -4 +/** return value request timeout */ +#define EC_TIMEOUT -5 + +/* Constants */ + +/** maximum EtherCAT frame length in bytes */ +#define EC_MAXECATFRAME 1518 +/** size of DC datagram used in first LRW frame */ +#define EC_FIRSTDCDATAGRAM 20 +/** datagram type EtherCAT */ +#define EC_ECATTYPE 0x1000 +/** maximum EtherCAT LRW frame length in bytes */ +/* MTU - Ethernet header - length - datagram header - WCK - FCS */ +#define EC_MAXLRWDATA (EC_MAXECATFRAME - 14 - 2 - 10 - 2 - 4) + +/** definition for frame buffers */ +typedef uint8 ec_bufT[EC_BUFSIZE]; + +/** ethernet header definition */ +OSAL_PACKED_BEGIN +typedef struct OSAL_PACKED +{ + /** destination MAC */ + uint16 da0, da1, da2; + /** source MAC */ + uint16 sa0, sa1, sa2; + /** ethernet type */ + uint16 etype; +} ec_etherheadert; +OSAL_PACKED_END + +/** ethernet header size */ +#define ETH_HEADERSIZE sizeof(ec_etherheadert) + +/** EtherCAT datagram header definition */ +OSAL_PACKED_BEGIN +typedef struct OSAL_PACKED +{ + /** length of EtherCAT datagram */ + uint16 elength; + /** EtherCAT command, see ec_cmdtype */ + uint8 command; + /** index, used in SOEM for Tx to Rx recombination */ + uint8 index; + /** ADP */ + uint16 ADP; + /** ADO */ + uint16 ADO; + /** length of data portion in datagram */ + uint16 dlength; + /** interrupt, currently unused */ + uint16 irpt; +} ec_comt; +OSAL_PACKED_END + +/** EtherCAT header size */ +#define EC_HEADERSIZE sizeof(ec_comt) +/** size of ec_comt.elength item in EtherCAT header */ +#define EC_ELENGTHSIZE sizeof(uint16) +/** offset position of command in EtherCAT header */ +#define EC_CMDOFFSET EC_ELENGTHSIZE +/** size of workcounter item in EtherCAT datagram */ +#define EC_WKCSIZE sizeof(uint16) +/** definition of datagram follows bit in ec_comt.dlength */ +#define EC_DATAGRAMFOLLOWS (1 << 15) + +/** Possible error codes returned. */ +typedef enum +{ + /** No error */ + EC_ERR_OK = 0, + /** Library already initialized. */ + EC_ERR_ALREADY_INITIALIZED, + /** Library not initialized. */ + EC_ERR_NOT_INITIALIZED, + /** Timeout occurred during execution of the function. */ + EC_ERR_TIMEOUT, + /** No slaves were found. */ + EC_ERR_NO_SLAVES, + /** Function failed. */ + EC_ERR_NOK +} ec_err; + +/** Possible EtherCAT slave states */ +typedef enum +{ + /** No valid state. */ + EC_STATE_NONE = 0x00, + /** Init state*/ + EC_STATE_INIT = 0x01, + /** Pre-operational. */ + EC_STATE_PRE_OP = 0x02, + /** Boot state*/ + EC_STATE_BOOT = 0x03, + /** Safe-operational. */ + EC_STATE_SAFE_OP = 0x04, + /** Operational */ + EC_STATE_OPERATIONAL = 0x08, + /** Error or ACK error */ + EC_STATE_ACK = 0x10, + EC_STATE_ERROR = 0x10 +} ec_state; + +/** Possible buffer states */ +typedef enum +{ + /** Empty */ + EC_BUF_EMPTY = 0x00, + /** Allocated, but not filled */ + EC_BUF_ALLOC = 0x01, + /** Transmitted */ + EC_BUF_TX = 0x02, + /** Received, but not consumed */ + EC_BUF_RCVD = 0x03, + /** Cycle completed */ + EC_BUF_COMPLETE = 0x04 +} ec_bufstate; + +/** Ethercat data types */ +typedef enum +{ + ECT_BOOLEAN = 0x0001, + ECT_INTEGER8 = 0x0002, + ECT_INTEGER16 = 0x0003, + ECT_INTEGER32 = 0x0004, + ECT_UNSIGNED8 = 0x0005, + ECT_UNSIGNED16 = 0x0006, + ECT_UNSIGNED32 = 0x0007, + ECT_REAL32 = 0x0008, + ECT_VISIBLE_STRING = 0x0009, + ECT_OCTET_STRING = 0x000A, + ECT_UNICODE_STRING = 0x000B, + ECT_TIME_OF_DAY = 0x000C, + ECT_TIME_DIFFERENCE = 0x000D, + ECT_DOMAIN = 0x000F, + ECT_INTEGER24 = 0x0010, + ECT_REAL64 = 0x0011, + ECT_INTEGER64 = 0x0015, + ECT_UNSIGNED24 = 0x0016, + ECT_UNSIGNED64 = 0x001B, + ECT_BIT1 = 0x0030, + ECT_BIT2 = 0x0031, + ECT_BIT3 = 0x0032, + ECT_BIT4 = 0x0033, + ECT_BIT5 = 0x0034, + ECT_BIT6 = 0x0035, + ECT_BIT7 = 0x0036, + ECT_BIT8 = 0x0037 +} ec_datatype; + +/** Ethercat command types */ +typedef enum +{ + /** No operation */ + EC_CMD_NOP = 0x00, + /** Auto Increment Read */ + EC_CMD_APRD, + /** Auto Increment Write */ + EC_CMD_APWR, + /** Auto Increment Read Write */ + EC_CMD_APRW, + /** Configured Address Read */ + EC_CMD_FPRD, + /** Configured Address Write */ + EC_CMD_FPWR, + /** Configured Address Read Write */ + EC_CMD_FPRW, + /** Broadcast Read */ + EC_CMD_BRD, + /** Broadcast Write */ + EC_CMD_BWR, + /** Broadcast Read Write */ + EC_CMD_BRW, + /** Logical Memory Read */ + EC_CMD_LRD, + /** Logical Memory Write */ + EC_CMD_LWR, + /** Logical Memory Read Write */ + EC_CMD_LRW, + /** Auto Increment Read Multiple Write */ + EC_CMD_ARMW, + /** Configured Read Multiple Write */ + EC_CMD_FRMW + /** Reserved */ +} ec_cmdtype; + +/** Ethercat EEprom command types */ +typedef enum +{ + /** No operation */ + EC_ECMD_NOP = 0x0000, + /** Read */ + EC_ECMD_READ = 0x0100, + /** Write */ + EC_ECMD_WRITE = 0x0201, + /** Reload */ + EC_ECMD_RELOAD = 0x0300 +} ec_ecmdtype; + +/** EEprom state machine read size */ +#define EC_ESTAT_R64 0x0040 +/** EEprom state machine busy flag */ +#define EC_ESTAT_BUSY 0x8000 +/** EEprom state machine error flag mask */ +#define EC_ESTAT_EMASK 0x7800 +/** EEprom state machine error acknowledge */ +#define EC_ESTAT_NACK 0x2000 + +/* Ethercat SSI (Slave Information Interface) */ + +/** Start address SII sections in Eeprom */ +#define ECT_SII_START 0x0040 + +enum +{ + /** SII category strings */ + ECT_SII_STRING = 10, + /** SII category general */ + ECT_SII_GENERAL = 30, + /** SII category FMMU */ + ECT_SII_FMMU = 40, + /** SII category SM */ + ECT_SII_SM = 41, + /** SII category PDO */ + ECT_SII_PDO = 50 +}; + +/** Item offsets in SII general section */ +enum +{ + ECT_SII_MANUF = 0x0008, + ECT_SII_ID = 0x000a, + ECT_SII_REV = 0x000c, + ECT_SII_SER = 0x000e, + ECT_SII_BOOTRXMBX = 0x0014, + ECT_SII_BOOTTXMBX = 0x0016, + ECT_SII_MBXSIZE = 0x0019, + ECT_SII_TXMBXADR = 0x001a, + ECT_SII_RXMBXADR = 0x0018, + ECT_SII_MBXPROTO = 0x001c +}; + +/** Mailbox types definitions */ +enum +{ + /** Error mailbox type */ + ECT_MBXT_ERR = 0x00, + /** ADS over EtherCAT mailbox type */ + ECT_MBXT_AOE, + /** Ethernet over EtherCAT mailbox type */ + ECT_MBXT_EOE, + /** CANopen over EtherCAT mailbox type */ + ECT_MBXT_COE, + /** File over EtherCAT mailbox type */ + ECT_MBXT_FOE, + /** Servo over EtherCAT mailbox type */ + ECT_MBXT_SOE, + /** Vendor over EtherCAT mailbox type */ + ECT_MBXT_VOE = 0x0f +}; + +/** CoE mailbox types */ +enum +{ + ECT_COES_EMERGENCY = 0x01, + ECT_COES_SDOREQ, + ECT_COES_SDORES, + ECT_COES_TXPDO, + ECT_COES_RXPDO, + ECT_COES_TXPDO_RR, + ECT_COES_RXPDO_RR, + ECT_COES_SDOINFO +}; + +/** CoE SDO commands */ +enum +{ + ECT_SDO_DOWN_INIT = 0x21, + ECT_SDO_DOWN_EXP = 0x23, + ECT_SDO_DOWN_INIT_CA = 0x31, + ECT_SDO_UP_REQ = 0x40, + ECT_SDO_UP_REQ_CA = 0x50, + ECT_SDO_SEG_UP_REQ = 0x60, + ECT_SDO_ABORT = 0x80 +}; + +/** CoE Object Description commands */ +enum +{ + ECT_GET_ODLIST_REQ = 0x01, + ECT_GET_ODLIST_RES = 0x02, + ECT_GET_OD_REQ = 0x03, + ECT_GET_OD_RES = 0x04, + ECT_GET_OE_REQ = 0x05, + ECT_GET_OE_RES = 0x06, + ECT_SDOINFO_ERROR = 0x07 +}; + +/** FoE opcodes */ +enum +{ + ECT_FOE_READ = 0x01, + ECT_FOE_WRITE, + ECT_FOE_DATA, + ECT_FOE_ACK, + ECT_FOE_ERROR, + ECT_FOE_BUSY +}; + +/** SoE opcodes */ +enum +{ + ECT_SOE_READREQ = 0x01, + ECT_SOE_READRES, + ECT_SOE_WRITEREQ, + ECT_SOE_WRITERES, + ECT_SOE_NOTIFICATION, + ECT_SOE_EMERGENCY +}; + +/** Ethercat registers */ +enum +{ + ECT_REG_TYPE = 0x0000, + ECT_REG_PORTDES = 0x0007, + ECT_REG_ESCSUP = 0x0008, + ECT_REG_STADR = 0x0010, + ECT_REG_ALIAS = 0x0012, + ECT_REG_DLCTL = 0x0100, + ECT_REG_DLPORT = 0x0101, + ECT_REG_DLALIAS = 0x0103, + ECT_REG_DLSTAT = 0x0110, + ECT_REG_ALCTL = 0x0120, + ECT_REG_ALSTAT = 0x0130, + ECT_REG_ALSTATCODE = 0x0134, + ECT_REG_PDICTL = 0x0140, + ECT_REG_IRQMASK = 0x0200, + ECT_REG_RXERR = 0x0300, + ECT_REG_FRXERR = 0x0308, + ECT_REG_EPUECNT = 0x030C, + ECT_REG_PECNT = 0x030D, + ECT_REG_PECODE = 0x030E, + ECT_REG_LLCNT = 0x0310, + ECT_REG_WDCNT = 0x0442, + ECT_REG_EEPCFG = 0x0500, + ECT_REG_EEPCTL = 0x0502, + ECT_REG_EEPSTAT = 0x0502, + ECT_REG_EEPADR = 0x0504, + ECT_REG_EEPDAT = 0x0508, + ECT_REG_FMMU0 = 0x0600, + ECT_REG_FMMU1 = ECT_REG_FMMU0 + 0x10, + ECT_REG_FMMU2 = ECT_REG_FMMU1 + 0x10, + ECT_REG_FMMU3 = ECT_REG_FMMU2 + 0x10, + ECT_REG_SM0 = 0x0800, + ECT_REG_SM1 = ECT_REG_SM0 + 0x08, + ECT_REG_SM2 = ECT_REG_SM1 + 0x08, + ECT_REG_SM3 = ECT_REG_SM2 + 0x08, + ECT_REG_SM0STAT = ECT_REG_SM0 + 0x05, + ECT_REG_SM1STAT = ECT_REG_SM1 + 0x05, + ECT_REG_SM1ACT = ECT_REG_SM1 + 0x06, + ECT_REG_SM1CONTR = ECT_REG_SM1 + 0x07, + ECT_REG_DCTIME0 = 0x0900, + ECT_REG_DCTIME1 = 0x0904, + ECT_REG_DCTIME2 = 0x0908, + ECT_REG_DCTIME3 = 0x090C, + ECT_REG_DCSYSTIME = 0x0910, + ECT_REG_DCSOF = 0x0918, + ECT_REG_DCSYSOFFSET = 0x0920, + ECT_REG_DCSYSDELAY = 0x0928, + ECT_REG_DCSYSDIFF = 0x092C, + ECT_REG_DCSPEEDCNT = 0x0930, + ECT_REG_DCTIMEFILT = 0x0934, + ECT_REG_DCCUC = 0x0980, + ECT_REG_DCSYNCACT = 0x0981, + ECT_REG_DCSTART0 = 0x0990, + ECT_REG_DCCYCLE0 = 0x09A0, + ECT_REG_DCCYCLE1 = 0x09A4 +}; + +/** standard SDO Sync Manager Communication Type */ +#define ECT_SDO_SMCOMMTYPE 0x1c00 +/** standard SDO PDO assignment */ +#define ECT_SDO_PDOASSIGN 0x1c10 +/** standard SDO RxPDO assignment */ +#define ECT_SDO_RXPDOASSIGN 0x1c12 +/** standard SDO TxPDO assignment */ +#define ECT_SDO_TXPDOASSIGN 0x1c13 + +/** Ethercat packet type */ +#define ETH_P_ECAT 0x88A4 + +/** Error types */ +typedef enum +{ + EC_ERR_TYPE_SDO_ERROR = 0, + EC_ERR_TYPE_EMERGENCY = 1, + EC_ERR_TYPE_PACKET_ERROR = 3, + EC_ERR_TYPE_SDOINFO_ERROR = 4, + EC_ERR_TYPE_FOE_ERROR = 5, + EC_ERR_TYPE_FOE_BUF2SMALL = 6, + EC_ERR_TYPE_FOE_PACKETNUMBER = 7, + EC_ERR_TYPE_SOE_ERROR = 8, + EC_ERR_TYPE_MBX_ERROR = 9, + EC_ERR_TYPE_FOE_FILE_NOTFOUND = 10, + EC_ERR_TYPE_EOE_INVALID_RX_DATA = 11 +} ec_err_type; + +/** Struct to retrieve errors. */ +typedef struct +{ + /** Time at which the error was generated. */ + ec_timet Time; + /** Signal bit, error set but not read */ + boolean Signal; + /** Slave number that generated the error */ + uint16 Slave; + /** CoE SDO index that generated the error */ + uint16 Index; + /** CoE SDO subindex that generated the error */ + uint8 SubIdx; + /** Type of error */ + ec_err_type Etype; + union + { + /** General abortcode */ + int32 AbortCode; + /** Specific error for Emergency mailbox */ + struct + { + uint16 ErrorCode; + uint8 ErrorReg; + uint8 b1; + uint16 w1; + uint16 w2; + }; + }; +} ec_errort; + +/** Helper macros */ + +/** Set the count value in the Mailbox header */ +#define MBX_HDR_SET_CNT(cnt) ((uint8)((cnt) << 4)) + +/** Macro to make a word from 2 bytes */ +#define MK_WORD(msb, lsb) ((((uint16)(msb)) << 8) | (lsb)) +/** Macro to get hi byte of a word */ +#define HI_BYTE(w) ((w) >> 8) +/** Macro to get low byte of a word */ +#define LO_BYTE(w) ((w) & 0x00ff) +/** Macro to swap hi and low byte of a word */ +#define SWAP(w) ((((w) & 0xff00) >> 8) | (((w) & 0x00ff) << 8)) +/** Macro to get hi word of a dword */ +#define LO_WORD(l) ((l) & 0xffff) +/** Macro to get hi word of a dword */ +#define HI_WORD(l) ((l) >> 16) + +#define get_unaligned(ptr) \ + ({ __typeof__(*(ptr)) __tmp; memcpy(&__tmp, (ptr), sizeof(*(ptr))); __tmp; }) + +#define put_unaligned32(val, ptr) \ + (memcpy((ptr), &(val), 4)) + +#define put_unaligned64(val, ptr) \ + (memcpy((ptr), &(val), 8)) + +#if !defined(EC_BIG_ENDIAN) && defined(EC_LITTLE_ENDIAN) + +#define htoes(A) (A) +#define htoel(A) (A) +#define htoell(A) (A) +#define etohs(A) (A) +#define etohl(A) (A) +#define etohll(A) (A) + +#elif !defined(EC_LITTLE_ENDIAN) && defined(EC_BIG_ENDIAN) + +#define htoes(A) ((((uint16)(A) & 0xff00) >> 8) | \ + (((uint16)(A) & 0x00ff) << 8)) +#define htoel(A) ((((uint32)(A) & 0xff000000) >> 24) | \ + (((uint32)(A) & 0x00ff0000) >> 8) | \ + (((uint32)(A) & 0x0000ff00) << 8) | \ + (((uint32)(A) & 0x000000ff) << 24)) +#define htoell(A) ((((uint64)(A) & (uint64)0xff00000000000000ULL) >> 56) | \ + (((uint64)(A) & (uint64)0x00ff000000000000ULL) >> 40) | \ + (((uint64)(A) & (uint64)0x0000ff0000000000ULL) >> 24) | \ + (((uint64)(A) & (uint64)0x000000ff00000000ULL) >> 8) | \ + (((uint64)(A) & (uint64)0x00000000ff000000ULL) << 8) | \ + (((uint64)(A) & (uint64)0x0000000000ff0000ULL) << 24) | \ + (((uint64)(A) & (uint64)0x000000000000ff00ULL) << 40) | \ + (((uint64)(A) & (uint64)0x00000000000000ffULL) << 56)) + +#define etohs htoes +#define etohl htoel +#define etohll htoell + +#else + +#error "Must define one of EC_BIG_ENDIAN or EC_LITTLE_ENDIAN" + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _EC_TYPE_H */ diff --git a/dependency/x86/third_party/soem/include/soem/nicdrv.h b/dependency/x86/third_party/soem/include/soem/nicdrv.h new file mode 100644 index 00000000..010d8efc --- /dev/null +++ b/dependency/x86/third_party/soem/include/soem/nicdrv.h @@ -0,0 +1,108 @@ +/* + * This software is dual-licensed under GPLv3 and a commercial + * license. See the file LICENSE.md distributed with this software for + * full license information. + */ + +/** \file + * \brief + * Headerfile for nicdrv.c + */ + +#ifndef _nicdrvh_ +#define _nicdrvh_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** pointer structure to Tx and Rx stacks */ +typedef struct +{ + /** socket connection used */ + int *sock; + /** tx buffer */ + ec_bufT (*txbuf)[EC_MAXBUF]; + /** tx buffer lengths */ + int (*txbuflength)[EC_MAXBUF]; + /** temporary receive buffer */ + ec_bufT *tempbuf; + /** rx buffers */ + ec_bufT (*rxbuf)[EC_MAXBUF]; + /** rx buffer status fields */ + int (*rxbufstat)[EC_MAXBUF]; + /** received MAC source address (middle word) */ + int (*rxsa)[EC_MAXBUF]; + /** number of received frames */ + uint64 rxcnt; +} ec_stackT; + +/** pointer structure to buffers for redundant port */ +typedef struct +{ + ec_stackT stack; + int sockhandle; + /** rx buffers */ + ec_bufT rxbuf[EC_MAXBUF]; + /** rx buffer status */ + int rxbufstat[EC_MAXBUF]; + /** rx MAC source address */ + int rxsa[EC_MAXBUF]; + /** temporary rx buffer */ + ec_bufT tempinbuf; +} ecx_redportt; + +/** pointer structure to buffers, vars and mutexes for port instantiation */ +typedef struct +{ + ec_stackT stack; + int sockhandle; + /** rx buffers */ + ec_bufT rxbuf[EC_MAXBUF]; + /** rx buffer status */ + int rxbufstat[EC_MAXBUF]; + /** rx MAC source address */ + int rxsa[EC_MAXBUF]; + /** temporary rx buffer */ + ec_bufT tempinbuf; + /** temporary rx buffer status */ + int tempinbufs; + /** transmit buffers */ + ec_bufT txbuf[EC_MAXBUF]; + /** transmit buffer lengths */ + int txbuflength[EC_MAXBUF]; + /** temporary tx buffer */ + ec_bufT txbuf2; + /** temporary tx buffer length */ + int txbuflength2; + /** last used frame index */ + uint8 lastidx; + /** current redundancy state */ + int redstate; + /** pointer to redundancy port and buffers */ + ecx_redportt *redport; + pthread_mutex_t getindex_mutex; + pthread_mutex_t tx_mutex; + pthread_mutex_t rx_mutex; +} ecx_portt; + +extern const uint16 priMAC[3]; +extern const uint16 secMAC[3]; + +void ec_setupheader(void *p); +int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary); +int ecx_closenic(ecx_portt *port); +void ecx_setbufstat(ecx_portt *port, uint8 idx, int bufstat); +uint8 ecx_getindex(ecx_portt *port); +int ecx_outframe(ecx_portt *port, uint8 idx, int sock); +int ecx_outframe_red(ecx_portt *port, uint8 idx); +int ecx_waitinframe(ecx_portt *port, uint8 idx, int timeout); +int ecx_srconfirm(ecx_portt *port, uint8 idx, int timeout); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dependency/x86/third_party/soem/include/soem/osal.h b/dependency/x86/third_party/soem/include/soem/osal.h new file mode 100644 index 00000000..f88bce85 --- /dev/null +++ b/dependency/x86/third_party/soem/include/soem/osal.h @@ -0,0 +1,222 @@ +/* + * This software is dual-licensed under GPLv3 and a commercial + * license. See the file LICENSE.md distributed with this software for + * full license information. + */ + +#ifndef _osal_ +#define _osal_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "osal_defs.h" +#include +#include + +/* General types */ +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif +typedef uint8_t boolean; +typedef int8_t int8; +typedef int16_t int16; +typedef int32_t int32; +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +typedef int64_t int64; +typedef uint64_t uint64; +typedef float float32; +typedef double float64; + +typedef struct osal_timer +{ + ec_timet stop_time; +} osal_timert; + +/** + * @brief Returns monotonic time from some unspecified moment in the + * past. + * + * This time must be strictly increasing. It is used for time + * intervals measurement. + * + * @param ts Pointer to an ec_timet structure where the time will be + * stored. + */ +void osal_get_monotonic_time(ec_timet *ts); + +/** + * @brief Returns the current time. + * + * This time is used to set the initial EtherCAT network DC time and + * for logging purposes. + * + * @return ec_timet containing the current time. + */ +ec_timet osal_current_time(void); + +/** + * @brief Calculates the difference between two timestamps. + * + * @param start Pointer to the start timestamp. + * @param end Pointer to the end timestamp. + * @param diff Pointer to an ec_timet structure where the difference + * will be stored. + */ +void osal_time_diff(ec_timet *start, ec_timet *end, ec_timet *diff); + +/** + * @brief Starts the timer with a specified timeout. + * + * @param self Pointer to the timer object. + * @param timeout_usec Timeout in microseconds. + */ +void osal_timer_start(osal_timert *self, uint32 timeout_usec); + +/** + * @brief Checks if the timer has expired. + * + * @param self Pointer to the timer object. + * @return True if the timer is expired, false otherwise. + */ +boolean osal_timer_is_expired(osal_timert *self); + +/** + * @brief Sleeps for a specified duration in microseconds. + * + * @param usec Duration in microseconds. + * @return 0 on success, -1 on failure. + */ +int osal_usleep(uint32 usec); + +/** + * @brief Sleeps until the specified monotonic time. + * + * @param ts Pointer to an ec_timet structure representing the + * absolute time to sleep until. + * @return 0 on success, -1 on failure. + */ +int osal_monotonic_sleep(ec_timet *ts); + +/** + * @brief Allocates memory of the specified size. + * + * @param size Size in bytes to allocate. + * @return Pointer to the allocated memory or NULL on failure. + */ +void *osal_malloc(size_t size); + +/** + * @brief Frees the allocated memory. + * + * @param ptr Pointer to the memory to free. + */ +void osal_free(void *ptr); + +/** + * @brief Creates a new thread. + * + * @param thandle Pointer to the thread handle which will store the + * thread ID. + * @param stacksize Size of the stack for the new thread. + * @param func Pointer to the function to execute in the new thread. + * @param param Pointer to parameters to pass to the thread function. + * @return 1 on success, 0 on failure. + */ +int osal_thread_create(void *thandle, int stacksize, void *func, void *param); + +/** + * @brief Creates a new real-time thread. + * + * @param thandle Pointer to the thread handle which will store the + * thread ID. + * @param stacksize Size of the stack for the new thread. + * @param func Pointer to the function to execute in the new thread. + * @param param Pointer to parameters to pass to the thread function. + * @return 1 on success, 0 on failure. + */ +int osal_thread_create_rt(void *thandle, int stacksize, void *func, void *param); + +/** + * @brief Creates a mutex. + * + * @return Pointer to the created mutex or NULL on failure. + */ +void *osal_mutex_create(void); + +/** + * @brief Destroys a mutex. + * + * @param mutex Pointer to the mutex to destroy. + */ +void osal_mutex_destroy(void *mutex); + +/** + * @brief Locks the mutex. + * + * @param mutex Pointer to the mutex to lock. + */ +void osal_mutex_lock(void *mutex); + +/** + * @brief Unlocks the mutex. + * + * @param mutex Pointer to the mutex to unlock. + */ +void osal_mutex_unlock(void *mutex); + +#ifndef osal_timespec_from_usec +#define osal_timespec_from_usec(usec, result) \ + do \ + { \ + (result)->tv_sec = usec / 1000000; \ + (result)->tv_nsec = (usec % 1000000) * 1000; \ + } while (0) +#endif + +#ifndef osal_timespeccmp +#define osal_timespeccmp(a, b, CMP) \ + (((a)->tv_sec == (b)->tv_sec) \ + ? ((a)->tv_nsec CMP(b)->tv_nsec) \ + : ((a)->tv_sec CMP(b)->tv_sec)) +#endif + +#ifndef osal_timespecadd +#define osal_timespecadd(a, b, result) \ + do \ + { \ + (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \ + (result)->tv_nsec = (a)->tv_nsec + (b)->tv_nsec; \ + if ((result)->tv_nsec >= 1000000000) \ + { \ + ++(result)->tv_sec; \ + (result)->tv_nsec -= 1000000000; \ + } \ + } while (0) +#endif + +#ifndef osal_timespecsub +#define osal_timespecsub(a, b, result) \ + do \ + { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \ + if ((result)->tv_nsec < 0) \ + { \ + --(result)->tv_sec; \ + (result)->tv_nsec += 1000000000; \ + } \ + } while (0) +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dependency/x86/third_party/soem/include/soem/osal_defs.h b/dependency/x86/third_party/soem/include/soem/osal_defs.h new file mode 100644 index 00000000..3d6ad511 --- /dev/null +++ b/dependency/x86/third_party/soem/include/soem/osal_defs.h @@ -0,0 +1,46 @@ +/* + * This software is dual-licensed under GPLv3 and a commercial + * license. See the file LICENSE.md distributed with this software for + * full license information. + */ + +#ifndef _osal_defs_ +#define _osal_defs_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +// define if debug printf is needed +#ifdef EC_DEBUG +#include +#define EC_PRINT printf +#else +#define EC_PRINT(...) \ + do \ + { \ + } while (0) +#endif + +#ifndef OSAL_PACKED +#define OSAL_PACKED_BEGIN +#define OSAL_PACKED __attribute__((__packed__)) +#define OSAL_PACKED_END +#endif + +#define ec_timet struct timespec + +#define OSAL_THREAD_HANDLE pthread_t * +#define OSAL_THREAD_FUNC void +#define OSAL_THREAD_FUNC_RT void + +#define osal_mutext pthread_mutex_t + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dependency/x86/third_party/soem/include/soem/soem.h b/dependency/x86/third_party/soem/include/soem/soem.h new file mode 100644 index 00000000..e4d43a8e --- /dev/null +++ b/dependency/x86/third_party/soem/include/soem/soem.h @@ -0,0 +1,28 @@ +/* + * This software is dual-licensed under GPLv3 and a commercial + * license. See the file LICENSE.md distributed with this software for + * full license information. + */ + +/** \file + * \brief + * SOEM main include file + */ + +#ifndef _SOEM_H +#define _SOEM_H + +#include "soem/ec_options.h" +#include "soem/ec_type.h" +#include "nicdrv.h" +#include "soem/ec_base.h" +#include "soem/ec_main.h" +#include "soem/ec_dc.h" +#include "soem/ec_coe.h" +#include "soem/ec_foe.h" +#include "soem/ec_soe.h" +#include "soem/ec_eoe.h" +#include "soem/ec_config.h" +#include "soem/ec_print.h" + +#endif /* _SOEM_H */ diff --git a/dependency/x86/third_party/soem/lib/libsoem.a b/dependency/x86/third_party/soem/lib/libsoem.a new file mode 100644 index 0000000000000000000000000000000000000000..511036788210ffebe1d4cbac55e6e92e078f23dd GIT binary patch literal 691060 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
@#Bt`JVn(TT0@sQ_Mu0e}3YA@jzh@%G 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
{3lORijLp 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�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%0DBnA z*V4jlg+x+V8~ciECiKwXOG0rvR&kzq=w{kzBO~x}RT!s)}9GrJw8uj&}ot z-9T?QkmCk?EHJrl5~VB<~9uB&rE?Q4X*Q4ldJ(X!p({% zu@Qckf+xj^r`##|4Q== z)y(f-X`Uqk4$nWOsqnm7!t;SNPk2^JY8qKFQ0#Vqx&*H}iSH^`mghd`JT$$DudqAp z)}axnDRw+7N?T4(Q{#S09x}@zh~qn}G;M`P4hjtPEF(@s9`c}~N%W8{DR?de&pj!4 zE_CrEdFV|x>HzNkbRx=z6<&gk$m1?c@{Cb5Zh3MiYTAtCvWcdkF8gZ_8TY+InAq#r z?74fro;y@IzTSGl1m(GV(~=XLy=jH+4JYiSd(*}wB{83QJK`7P3w`w zq?;GV1!iE`g1MEX^-oITC#3atGl7fA0k<)b_0ZfcZTUYr=|QHx8+p7pQx|{lm)281 z<9{PlPvhDj`U)cdx3bri`S_7(y}l;t{=dw}w~zq8Fduhf2u(LWLP1w!AeMONCay3* zS#1}1hfN+IcqvOf-*pGAnz8(|MJs%=Pi%4q<+Tc!lCgZB10Wos&FygH%!5>5VGnT$ zKw08%J=ILkA}3m)4IH#*rR(uO$@8L@(4QI4SpCaytxKwtBBiYv5({@??5@Nv7q?P; zA>m$c#QVYa^;Qy`Bi_A6@@m+lH)^D5{=VFLc?La-GkdyWZv<1Qn>1E7$R7rv2MV0Hr@Ct71rhvTbeR z2D{B`uqzyc(NvBNcAJs>(Cs!O`Jvlw#sv4$9vfhV7|gf4itTrYfIKSP>9v<*A99=^ z>V~G0n|S;Hob2XLq0D@*GT_38-b(v1S(*7pa^5oE@T}J>y7BI{P?mV1SK#mh$^nxYt?$|(8&;h_7_evj&PemvY@cxdE$48*aC9@=M-hla0;&or|^of zk-WqoHYO)!ErU~J5Q7cRsyTO$!3Gb_eoVj^KFa7y?{@l^|61VuL&T56AUH*W6x%)M z-Jmjcn`d|O_D3e(0a0rVRGaN@POqataj6vZK5wOfw9u)}8;~}->&aMtQ=L7)pvy{q z^hBDpvFl6GXLYZyuTib5x3-n5*3pgR8Io>zmVPs?yR=60d;2Fls^M)3wz45Dfl%V8 zzaLuOi29r!s$QDO%yZ0UM;>f&yvX)IdYkJ{?qX1!N=!S{KFAe*PeT5X5-sw+XjJVU^+nwpDvIA4#_!Eq_7HpI|{Tu}S%O9Gr%AM>kH+1%u z+FkyxEaviu7jTi4{;Yd3W5i}8DRQ?z`K5!q{k`aoI{RSOzxL8`Z2gVjGne43)(4w# zf%p7*x)YJW%3(;(J_=OS7kE@m;p`91x1G-^_BRx{%vB6&@3Q$wfufU7ajlnEiZ_Is{F!f_jkNfe zy|NTeYNnM}LB>1impbb)ItEA8Q*{O{4kCb*MRzZ z4>hsG+HfmoM{zQG6X`d}WbW4*sNNkO5{ktxIXy2yP`&#+)b4t{juoN-g1u?5-}B`9 zO2?Z{IahhpU~ep8pBU^fbocAN^dkCT|7ldlKKj-GGsQiQW?Zc0dt*?>Twpvi+>9B( z@T=c8pNva^@l0SdF1-#Y&od=$3GtegF=(d)m#{$1s zK6)rUFo7<V8#4T!cOm-XVm`ryh%udZqYtoKgGMLU8F4e9DhXyXNsi$&* zcYPigpwCUyp|}3&0_6hl`jRKh@A^EGWpaUceI;%eK!d+8U)>XiwDkZOPq<(g_`v5G zcszalfJZr5*$)o!_K?BnyA8lJfjPW8#!Gi*D!LRsb51I{6pnUJD!LSowk;K13P<}Q z6xp)WH@MO_g`DyvEket4m}&F+*a77cdh7Iyl#jLf zJUNe_DB66U^j_ggsf>NV$sY4M8N1Ymf8$bY$)92jqAaV0R` z%;IWbJc&idCB)h@A-5MVBI6 z`C#XGH|Hv|Xq%g_unz}!XHm(!B@MeCC-Z&jd6m9&`qrU+&|ik4LvN*_jhm;^=b39N zoHsw5adC}S>GN!UMw?Cb@dxdMBf|SEZJM_g*{tUr!mK2~+e$}Qa^nlud+n-&j_lx} zS?r}5l1!sENP6ier=m;IU+1Nulb4LW{O(OfC;XE7mo2|qGt2SuG8R4XJAy2j7Esokt7lhy_KHEa>XC%$xEXj>7HGd!s>A^t7%RLp^XL1 z>hTn;9@jmqtctG4tt&hK6Y{M;5l=laebS3#x~Ed$+PyYmx~EgnZT8|Eb0yo3?esfQ znD$(-gZ)m_Q`c-Eb!mK%x7w_^#jCx|G~Q{ZM}MW7saj9ob6%}`_MG$PpK5)A+qv1SS9ndx zxGPiOu2SD|Q|-xgS9_c3-MQv2Zby8i%zZj*>5x@%0d;FIyDf4R$x^JSC3aS_hVF zy^yyD^ZtQuDWXt!uRi~#DMwKyKiP_(>Qp)Ka(sr5pr87=<0D(h@fu7#W>q$~;up7M3xdF5VYBa%TL5?TYEj1@hn`RF@ePF1vcIMYpU%wBK# zsl-Y2Yy?j%uquZoMAYFhkR6Jv;Y)1PH#|l?T4|%V(hlJ~g5K!kZhS!%x-IBPLyEtu zq?zN|mql8smW0-5&60#h*rN5)y+KY^~P+=E<>`@V)hc>>5q{=|EdFOr!YFTQ{L>5nVU&Acg+P zFW>>+J zY`yYQVplPS@Xir>*ydml1ezTN^ww5T<`L=XVg7WTD&<6;N8{;gSvy7N;zUn_ zksm;hQ~A%4{K_Vw@8W4^5j1KWe&|DE?03FVI>Gieg%kUFFRrCXZz4IxDbibi#v$vi z59n(b>8*2-(X_9{KAP2q-ufBJr|)vzN`K!4^#0rpeD4CnZgx7e!=UyKadR{9uqI`- zLBw(pv2whQbKE${+X_Zgu4W(W)P>&q6o!)Y)vjCV54wT%E@09Y0At;3L`>p!y<&*u zE!|yaGI8_pXPJ8wEBlzTQvris#6|D7UAG*KlG9L>9!=S`eYB|ygo|rYWr^!n`eZk7 zz6+RC4PdOBOSwKcf;&Xzk-VB@CPj z<5~K1u3PE5-M~jKV9-7QgZ@Q7!I}8!K|gu)Gn9Tt(oY0Gmz{y1x?@uBtKhf-F?##b zCtZiCf8k!Vs9Smw9y3(8bY8jL8i^4}c>Cy0O4vtlQpP@dlT!B4o0PMU-lU{`^d@EP zqcX^t&>1_N z7gKX>J?JGw`!=35K8mfwNJjWYMUa%+WJn<8a4?!)s`+vkfsrq&FVyS0>)kO7rfL41 z(F638(?(^LcK03LHV%(_jNvJeXe&EktRgtVwe0^yO3rxM)B>FKH!bCF^Csl6P5kLp$0y{q0Wy(ssFUZhOO z)UpNyNvN*6UdsA)8vr5c?-6zP>I5tF;JOCni0btBU=H)p;7_%ADFXP0tE!*PK-G`o zigU%F_(H!DH*M4aKgrR1c-%NgcubA2)q#5u26c?p7X(Sh>KE3svc_ilbMT3jEWHw~ zlV|hcmQYPLfRU1l&9!2%iMLru7h8m)1xIVgbw97670!Q@{vmE2pMy=$kZ>ZNT&=|)K zW@j447I)M*4u66qi+yfD231o4IL!qYvK(nHS&w(B6a}-U4lt6dAJFBfUP|B7$hq(dLq{;%^#&k`EVVsh zzzRd;Iz0=-XT*CVb?80=5X;JJoZIgw+Ev1o@hZ=>xdNL*%ARH0+{cYe7R?*rAI_b3 z615oDFquM`rcKF08&FRxCxegab#)GD)%|1=qN1yQWKQ=ifBz%;lYK*=s#HBRh`&*c z0EAdGRasD3Y?^Xv?&$cVl4L;qeK1x>xjI#YzlFvY4MM)kI;2Y8y%nbbgwnaCv7_rKAZzb+k82BZk$#J3z zAu{S)^qaXjew)3;F^H|U^gR|a$8Xy$x?XPJcN6}i3ciIE2p0EQWdQh!Zb+3rpHrrS z%Dj6E2~@Glz{MVr0m<}?v{m4YHTX7t%8B;LZBpX1T-}6`@}I{u<1w2()-ZUE)j%O;UA(w=>sc-F&K2%6V81sP`LG zH_cettUn0GlMMXc3Kr80{6Zl=9&*_#O;hb;m&VE>rByJT&6kvEy;$e@lfXbs(kJaACCOUh`*6PCUO!cQ{ri(B+T zIT8OfivBhLPU4nOzqhFkoH@ph|oCxx@;Rl5&ZFSWpdvM+%PaX z(Y`GPEN9mx2p&(aOrB4c#3{b_r}E%&K8wmOqzYbGtap`e<0b(1xf>~{ynUnI)<73GH|m?0kOugD zcS));z;0+FuJ|Rf%VBWxM2SGgu0@~L;U+l$o;k0HDb9~H>hIpfwn<_@8u_?4l4i^z zjlM#?O0h|!uR*Uegp~ro?#V_yZ64pj6?mu77n>1|nZIHnLdqRtPGZ^?w9hs^E4R~!7n**xdFe}01Heq@XNxi zu2WiKbQ5o&KBZ+N9?4;>S@7^eiv15S+*hyr3SpBZ#fyT7nFo6p3|j?JA6lfJXFRm%Zb-bc&bYe{ zG837Ij$2}ILZR&ug@6(tk24W>AQEWw6&&L{1HXT2fSh27Gg%UM#gg%Iu;&^meV&2e zC8YE{bq3DRo2W&7+?6ooDz-#_ut|91jP@jIoNgz>fCbLfOm^xZ21ziBtKnbghJT$p z_+;1vr}J@IzR15&Uu7&`boV0vEL_~Ts1>@OZ{YV2Xi~a|^Ph1^ca>M3uS`P%sXlUn z9ak@KIT9Kfy_BeMf_mU9N)ie^gJSCW6sA~e1ozh$LN=Iv4P0ol-SsEieR#5+p7GgU zUm|Zn@435h?OOygetYSn%!1tK?BPHQask*U!(W(Q2X2#ykDSdLpa<;fe)_VO#@kk2b!X|J{6; z+-`FedvQKB#*6d6o}V^+4v$Uq_s>r|kuUdyTCgk~{TTbMUo#I{teN+h8<3Oy<%UOZ z5G@|P;S(Sf9jF@Tq5j5sx6FgCx6J#5sO&iwvzQDPjD;3pyX=O$Z*U0)%<6ex%_Gsi znz!PHwBfT!v=ukpbwk>Te0dk>7532^-oF8as%jFkW_>M0Twl9%E|gz7_l~(D;vIAE zydFLzhfr03>Xx}si&)cI3wyWL-g!NJn3ap5#Hw0Ye^u@JTL1C~9V9B71~x>w zR*;b4<~b|pz}?0g&!5*Woudq}bk6cQ$}G$0+-_qZne&Q$`O2KvY=YMykSg|(Q<-f# z$mLWrhrc*Z0`kT4bKj5)yOdLA=Q;PB3v1tV?qlb{+K-*rex6vn{k(_Hk6W9mD*hjP z?;aRck^GO&?d1Sx3il{3pqKl&FqPXh1_*_wx`>E>**Vy?WfTe)HadQkCvP(fyU339Jm45#sNtNJ6$F-lScy%hAIG& z-7~cH_mZyl52v4=4$Yb${oM5{Zh*pDZg`m<_fFr9R{hQC9}<$OOA{I0-@pEs>y8kj^^oOS_goi;_L7$p_+$uhZXCP`Ptc%ZYyL z^#`w~Sv+|Ch8uXbYq{ZTS@`;f)6=Qt^z?70bIbYZkIW!jFh6=z@t=w*eiT1jLW(Zg zUm_K~zvNEUEDO~rpMhJOEC{Kpb_9so)QAyQ?jKb9LENUeG&)I zp4$|_rk^U>J_QnNpYrQeNbu{_Tc$|~Zke{CIA|)2RRpZ_Q(-FSr>-atiUJaAr$CIg zQ&!+Rou*@+D!P3N?Bez*+ouE_lgSi=3}FTx^QS4Rrjmh7eT^QsOj|$A>X;8q`FJYH zaLcskrqT1cX$Pi}V;-1xGYQ<5V?HqDjVXls#*~x%JUQjpDatl}opSS3WidBTT{cxg z|1k9hv-rZ)mrR70z>q>X78uM_oo*RqD*Gcb$b|#bj>v-9E6``@$v{ zK;(lW(=l%^x}(VIn2!|vu>cA`TX2pZuNHn#h(}}5?S#~ZW3DaORiMH1BI_v^r)>-uHRX@ArA1=EE^RmA#=~?yVe?aSDLLy-mk_EPro4l(;wl`Fztc zzsUO`kC^=-@5j7=V}6nMN9!&nH2>Ip?PwaQ0Kq>0EGjI(NmSR-AEj5t`r61!wQeX2ltLzlPq#+veOOxzN)q zxu6nc(KI@V23CK{xi?p_dT(xXu41)0_kI)laPF>L(&Mh&*GZ{@zNpi-tY z7+uQ5?3*%?R0|I;gj9zYezXv5f3)xms(rEWOG-e3hte1?WBHis-WO<8aDzvKriFZn<5gRxKipZi;L z6+@uj-^X~Fe}@ApZTELuYI>b!<2^8 z!nqwCnX#8iY`Ff*zPA9NW+MM3HGb{875d}jCg30TLClwZ(27ib^jAs50wwu#rsO;( zmfeYOXF`2%XD-c>YF(Q3dKLh$XYI|FfW6tTWdrb9_U0T0$c~o3ps_a-ir$-fG?T{3 z1jQngBHm{(#Uhg;08FvKq^OJ}z|ZVAjb7|WA7S^`dwp>O{BVH`c%q2qoLe;Mia0_ z0!{wpQ_?K}-48}XmJcMe1=#72d2kHO;lVL`#{j+;!+TKC<#S>sVP{I)NTCP5FfKmhST`55Ty zafEU)t*1vHBw7cV7Wp?xpy9Ws6fFWtS^yC(-A5ov?%kULr@Np_Hf8-!4u!Az%d>TT zJt&hktsHZ33_TByIW-3H;)gM-$AW{^V=o?G zXI%d*^&edSM{3hJ!fzV4X&m8i8n=5K$UHIbeR^z5+nxs6+tW^@u}mMP9U9MtL*tK* zr*1!wUpfJJODB+~Ax`U${$bh~>i<*Puk`p!`a9`({G8sLfj*CCyqJO7iy1FdZU4k0 z6J;iYLwj;A{Ue&hd`u#Zjfs$BqhO9L2gQ`MMkh`_BOth1hB6_9=r3pN1?MOS@3;kd zg=*g$9qs3rPZW{qjN_R*wQX$Xu`pwKSWQh>KUEu9ep`LE!o51S>JuPGF4ww=4u^Oo z@|(7(-39k--6cScT=sIzkrTJ>78r>f`!d}~Y0D<=mrBDXY}v%!6AdWMh{s!eoBlC$ z$j3-rQtmCGw9=q-HbZmtqdg3lVIXD^3kwMBp76v3Ne>r;tW}y6ODk?8$Z7-+9>)Kq zCMIdovBvOeJk_1+$lOvapLM&WPBxZud z<5YW`NX#_*@)HEMj6{6gGVSSC)61il;i&x``_nNhbVqxNm`>nY|E-!fv&NW{K1 zBafxfc7bN?b8zdrNxgS8P52J{Ql0$h8%I7o5^?h6$it)XSeXK1t(OW=gB}Wi@hd%k z1wFlf0pCBHzm7n0wl+PldsQ&3x(a(@bH_op0zxc4s6j}Z640~s1w zKRoIKBL2asuSmWNL^K~nzfDH%+vJ<6|II0&HY0GJX)+9U9~c>PvFg|-$tGI01bpZi zrn;lGPcSUd^v7WTWoCE57P6>Txd7Mg_ML;QbU`_^@Fc=5$IEW+Lm!Aovg z?j7IsoSxK&QhSF?sR^~0iR$>H1@n&E>{m-nef{wJNDk{qKR@iqHNlC-i*z!9&0jmV z=|`?PeNBu>lHQ;PT1fA$T8I(uLsQ{`c5!S*J=i+AfrM(i3+bs%2AEC#o@@3GhuJ(& zQ)(W4V*>VEwWA*_U`M~#6Xevto^Ub&fRhP-8y2)biB<$Oh6W~`39Rmi__rDawDqsv zq;vnt+@I=Ft3O~An&#IN-c1P3Wl(=0{B*x=^KJiW>rH=?^p8(!aU?ZxUrU9=f{3fxJ2_FpmEd;6l zH3J_W2x@;C{K{aq9DVKJKT%13wS`h6`p{FB^{h zemRiqX9r%O`h|f{4&wUZLGKJg{hdKiVMdUMwIsZrfV$E>X6A!o1h?k27?uY_7?$I; z+@ycp|7VPc!p{Si4#W&E9Y`~bIuQb*q|ZHvP{7J|KO5K-?FD`OlL@yD;rgvZHVq-0*)-%&67Z)XuQA}YA?GCE+>l!m3FDT;7ZSP4 z3yDV=a5V8}3D})AhsGlb78_Ix{S8u$U02{B~3~eAKHm8850ou49{DIAr z2{$LD+g29j& zG!Fj5V0h;(30o36EN+$H((l+ZN9041i|_c_A+y|gygepgYYk#C^dGJ|*&j3XasR&! zyC{EPQkFmZXD}~Zf%v%plLPR0asZ^BslPn%%s|x6kYV8>*}&#Os5O&W;d02JHG@%G zLxwz4e`WBO#P^qjwHJ8YsCh}2ABtrZY?ga=)_H1aAFT3 z&RnzUr+!slP8_$@gLrQXPQtWJiR5dm2MOGE4-&qu9;A)7dz?&In$&j7ZrCTT-nAj& z#;Z3DYqKSwQENe8iVz2H;(0CNdDZX(%yMRnFr}*pp>_43=0S3!%d?8t#F$RT;|V2a zB)k5SHjMhx0WcB1{6?jVtaLN1R0Xc1iLG7cJ>@HiH#)V2EHrRx1~=F@6sxmYqLVa+ z@kxL{W{}i4ly?WJ3}@wh77gc;@Dqtkw&~}9!a2IWdDwl!4EUcB&pVc2m3J(`D(_gR z8o|hx9ZL|0cPyLX(7a57G^qfm6WA>glDT7{GV!Fqmc4?$r|(yNv4nor_ui|JqTGAc zgICF=^}(xl0Kn4(oWp%F1Bd(WpvJ(03ivmD5p2KdyXs1K@Tx1h*?IzY+m$=6L~`@` zm8Y*nG(COgJy)T|l+9NJeOA)wEBij$*Rp`O^m(@r#(TF9Lj&VsUR(N{?1K?c_Ia=` zQR@2;b(@TfDSyDBjR*B#;~5&D4(xqIdtV=P zDmFXOfvY?__%8m=J`{xS>~n7)J_P|)(kTMsz337R#h9KzITQ9dQhCksPsW3ZC*!|} zw=mHh|2Q%6xP*cUGD!Vs{5$cuuqyLC++vD96>pzRBOHzYC|(VAlz_!d8$C|-{#S2M z|5xud@w9!5->e2fpD%mg6|aZ=MKFt0kp!qG+FEx5CepS*-rCapC8GLL?>Bm5-rnf_ zW^b**ZniRkqrFzd0b@nn&N%L=?~H39Kug@N-k6zPz4!LEQdI0h5b9TC_QQsDXWTvl z?2CIn4q^E9xRY@h@g%751v*71FiE7NUcdChH2u=+4{ny)x{h2;oUZ|-@zCnP%E^QWFt#GiT|>;=HV zUN7`&EgMi@=?NiTk*JXBr=EBB0^n|m%Ixc(pw+a@Wxek1N*xFNxFJoH>+0E*%Te_05Zt1$SE0lJy>nrqV z>GonbXyV0guXls8f9bZWJ8C<69OrPHP0fi+3;ld1KnM|HX06ef4+NLq@<$|dz#|{ z?%8y|xkv1O+M>zzytddFdJdLpIPq(qbH3H(co#9xHU{4bINs%7T^;zX-aA65_U{nV zH}w2{&p;Q+!24Z3??Q_GyvvEMa-libz+lT?=e-fX&#H{ED3t7?e(@wCE zPdojyleCb3cKW!p#X*QTVZs?ZzSj|x`CiAPowfCM+@v4ve6lm@Cp&-4^#EmAki90O z*O^#kQ)gnCJ{<6$ou2PZo3_qJJ7Yiqm<|qWK!*VqLXrr)v*U*yaT4_*58bSW#&H!7 z-7L?3C2lcriJyC|2f{RZEcEiv*v~p)=+7W4YQzB^L?p0#)K_-g-Vwm<9Y61cwTg8y zLv8G#iN=n zt;NjVTD!Xz!|kqptX9tKW3_9&fyf1{mKw}zOU=$&%;HY80m~Xen`8Jm<$$Xx!yI`{8WRm@Ds*#d2P|y+$Y7x;IHy7aIw~lL#cvGTKKXJt(v%WLw|$Q z9ufjQa-evk_K8|d!xOb{*YYjXw`(`nVFEYS?Wog!2MzA1dmDw2ZTj1F-=ctH^Ka|c z)@#4hx9Mx^x7K5Q-dew}9zBlMe?*Uu>VGBRul394X}`lU#`1Y6Ec_2yK*RER_n;2K zN9KJp&q3c64v7cR3-h2jxt8j0)Sjq?znrN3do5izxX^VtU9)6=mXZYJu<2hwIW)uP zYQg9FeM`XS+Ij~Hpo>w~)^9Rv7-dua!}U=(#ktURXdg!DO=CX{0k|uPz=e;%y`q8k zpXObd2ZJRR13+d0s%WVPr$_6-ygE+eIH#Wl0kX&C^A65~b`Q=wLKH}3)QFI@LQ@b( zv7wfvc&zpuKhMu?#a0@ z+LLqF)j;-jHQdLVYjTeQvj^6D9=d>s26#Db-Ahzk-s}G=H;5t?H8z_Y0(p2>)D8S4j?3!}x8Kd^fJ zyBoL9CGBpX+cFo9*aA_YyyxdGXUka*3DnZ5|6%Tb&4oihPfBu|Dz)iLD-TsdcZVuZ zRzjyID^FKSou01z+l@hGF0I^L31x1s{HPM6e1x{v$^@C`DxtaODo<3x-A_m|&?u9^ zk)!ldt6qrv&nrK#grYyMTvesbhe@xhdI$w*>Y=KGC_qyOtKLEZntH41D-@uqud0^a zsLh9_mfd(43eePDH$I92H1+6>hfsi~4&C?`3eXgNKy_UeBwttc`zq7v@J%h%_*mtK zm9P)y)B0vXUs};p0mZabykCL#_bWcEkc#=R;^#R4{5BEX2<^b@6BuTpgtGW;fD!!kK3Q*HibKXP&YI<|drzjXo%10_rRA72PtT;C(FsX`htkAP> zDTAc9lx-~o&epOA%cP_amc8r&;APKS9yGt@Ip&e@W1jPXo81FX(D`LHH1NypHDz?I zP+ub{Aya*@Y>$UQdn6Hnj(JW=&?(O+L>Hh>K-YR7v1Ru0*-+Q<*$&}_1z`T(GN|ZYIV2qO!LpY;0K6mz1c13V55~Fcin29jaFz$l_R(Ws*@-et!ZEOe z$61n5?jHjJ;%!aYnKFufXUfi%VUo|4ttdy&73Ft(P`lgnfCsftJZGs!1Sk^XW3Emz ze%@EUznp07$1~Jti}S)1QF*!C@Y?wP_R;b;xuKpK-Yow|5bGc1jh--c;afOpZ1mjW zF?-RlYCH@nIn3AZ!5}72!Q*-Txw3<0=GRvbmc2vbAM>0f?$3I@$3w2J0m0$6fwI1% zY)2VKvmHffXm>HEOYYk5@p~hNE#?eV}5vvk^+79 z%sM{{esq4;?%6Qs-LoH?E#2v{*#`hncoM4!SUYFKigu!nox@7zyIIiace8fPhMIRs zG6*6}248id&>>eL^Dp<+f1LH>EX}bLi_fezrJCdBZTgzhdr@e_T?-W8==YYklyYL! zQu+=GAvhaA0n_wO={KdC5Ape%(#F|9ymR(;dThtoPy!De36ra7tSM7POI^EMn8{tP zk6hs5BNsOZA}xBOc`}-xi-Cfklf6~g(0_3~8-q^I0tz!%3n>We%Q0pLY?kHgrN9fR zM?fqHrVS;(bfU34@K?XgFMu-mBXCR`Yegl1P9n7R@=9uegte;a0R=WJrvF z_f%2$L_^E>NHQ=aCPQEEkstlX_Kne$Vl+nI7Y&oV4~UpE!UB|3Xz@@WZ;#@9Bx-vU zu(n4%8O2}gdNS%@lr|40^rxsdsNs#MQ&E_t`=fV76PUIwM;O%VJvYd$hzhDdFe@vf zR$IXIkK3{ON!AW{6DH>{0KPSPOSENH0&vn?TeHHlwb4N-gdX~asBcNT-$tG2z(M3h zhw~kjJYV!u10Aal;>}VJoj)pxGfk}HN;y?sj zgnl66EdUq~ILE>;#<8%ssL^T|+%g$0Pex!ypOn2Zque{V^yy!Py&sMs{C@by;aWY| z`8fQp2#k6l;*|&-GtmCYJkwzA?+E)UjKA;wRoM4oG`Zj7i8=f}?0gtc?)k74;T$-c z!tV%2!-0qwBe?zw*ybNSU^)lGwno5!n46i(zVw5TxC3q^T;z90fg1$lEvTGf>U$|z z=}&KJx2GNC*wgN|FgV6-VYi0`{Rwl%%|&Ql6NdP-Mm9r#u)UAkts==3A>iHzQZa!e zBkL<_>fWj)U{7npZVPL}Y?zU@?961l#dy^K+pik8hJvqKLsy5kV*4#4(5THM)|Ev?upY`nmdfkrpKiGObcep1hTW|{rpxi&L<2kh z1z?N$T)Y(|#r(tPu@xZNqJfJ6)L$=Cz=8i#PO$OGT4yfo($4a7R$yFsn0T6z1K=fbaa7as;jKw$a zbZl{8;gjWQ-r$WKaQ-oJ!;#nh4OUP|BvA1a-XrT9jc1DxR zJfnRH0FSFPP7!dvq(;?SLMNM0bD3t`0uyRpk7a1F3B~1nyg>Ut>m~}=1Y>)Ux0Q!yCXF#%j z#&brn8I6Yos2maudp5Gk37(o{Cz`Dg{m#f$fCWm(976Q( z2qsez(0Vqq>wFs>KEAzK<13M^lBQ=Pe~t{U+GfsV>c83fy%XGi?|h>@27aUcJMAsp zHakx`!R<*WLxbElJI^`6?K$V0?IGfuvXkWYxbtPeh+8hv(S-g~d%zU8C(*U5?-Fk5 zcWOAsoOAAPALP~qD{jeNW_&yY{C+&+otYT=otdX*GQaw%nfK2M%mL#aCIdVy5fD#K z&0OyWV7>ePS!&`y6y%TvB1@5GuOE_ z9C{Lm8z6*=q57llciaeJ8)pH;Zxvo~zX^a%3s9LMsRXY$n)^*=*zno-he3#4v;5V} z58cKR3IEXjtsBYA&+eON;c@e(uM*P*~t z$S~}-2|7i;2faduv+KM;V$w!If5rV8;h06EnN-HTsihUnoBUBGv zY@Bru6H`UApQ>J_AAWTb1JNh=`(0GqGM@fk`uP!pn7{R^`=?~&ff(@^Xp6`Zte!p}BLj2Kqyj}h%-lku;YDGW&V87q@*PHwA=&yg) z|7Zf=q_FAd`X3#LOBQDbtNPKz-AVd;LvOpl5))4jCV0@b&LN z@oa{EHskvYJYAlf6aN@mmUi$8Qzk7Z=Xsv7CQzfq%>Cty}Htcbv?hT++|2w&O>u zY&w1^3IEsE@XsgN^k)8HB=xgMwx+A)+&9B4;5k1#Bk4hwjsH;S)!KwEH6E-Ny+Yc@ zPy_Xnf7tr?52swny_7`;8-gYN2`;|Cil4+&S6R6l{KE}4VYjv8ZiJ?#*oB@QBcctv zup3$!R#>4M*+wyHR|TJm5n0BF9szG$7nHVDahmq z2RDcGafxfVw2bK_GSyHc1m1F&6dUSbzoj0iLA{wB2RhV~&=ZgeoT`MI?_i$9%e6s8#3X`#muT~}j*u^K?kyo=|@?iF2n3;Rg=s~Zj%x`}X16)DY^AVMeM8CG2E7opn1 zWWSMBBKn~FxCRlLBNFBb$8{jSSdKI2un-20%z+waca;Fm-cf{($1Wx=&d?$%%wg?D zR2;?{!xk0`EgKD_rS5hSiwO=zvY;6UL_9z3VkkeoqB}kDH77J?q05bsF4l+=_Qyr1 zfv9gI#vva@TDQb6heExB&|ij@&>+Gyg?(9^2p@d0Uc+JWjwaCf))i9%#I83JOC?YP z>B*7k5@c8HVd~38G zAnfx**!RF&?-Jdqd21ttC0h(_G&abBu`EUmhytj?DAsL=u|N!f8bhM!k3kv@$-*D| zR{jGg%n_k35sw~yw~2UkGF}#aiKO8m-noz>x^N*yq#O2x3ej}|nx7F@GVoi4{+#mB zO3@Q=-48lYPE(c`pm`fBghN|IGokf#WKlC2BrHQ<5d30u=myxGvsgrGen&-eG7KwH zgmxl-gM}FLMK2oZ8k6SR-^l3gs|k7X-%yc~FGu$A~Kk*MAs8#Gqt%HDYSo zoksD0V9eeBcg7@Z=)O|eAI8WfE_@(xr0AX}ba+;Y3V#iOCqgPja8!xD9gHlVon*+q z!6m|CSkogK#Ff5Aph%w_ku18e5PgQT2ChTjcxqcFxOiGz*C^sE0GJ~Nqpjy@F}P9m zET-NriXDxjdqXm6O){npBV)kYFoB;i9(ryA-!-i!vp69O^Ek^dx@#R9X);~kfH)aB zDLs6gxI$Z;fY_0Y$r%7hC?>~=$w``q;x=^Xh^df~GMm-WUUY|5PY~U25cYe7o=bY^ zZP=U1x87l+)IkCe;b;)OeSp5q`bxb;I4BI1h#t(qdISeBQYB*N0h%IGQR_4Z#ltR+ z22BYGhW1%VF+BepKpo%GbRb5PYtiHwj^x9ld!x`Bjr}g+LJUWPtMPOe_;!59hV62k z6g=dHpuRdmh~E@P@;r_eMPI7H<=Ma^*q19})aMpl+^L6e&1D;8t5_71PnHDd&~im}ZPTJ;dg4?`Ttx*#`@7izY~-=m zg~UfEAetMR56+6#`fSm4l&D`Oy5278cZsf<6h}Ijf$J*8^|#ElOKUg`PmDQiIIcyv zc3K#0C|3o!JvfX zT8$-AZLm8n+z|(gjlwx(ju>zj95i6v#gZO|Nsq;f(jimCrijQ!Bry~v24K;H2-?CX z(U-iitLTgMZ!3N%o<=`He`D0rWLLlxaSb;{iq7;*6`klYSk_>yR7+5}--ZWm<~RyJJ}+1y`X@vW8J0o>o|#Ca~dv& zp=pkz{qR$)s&EKeC20boD9Sw|8~mdE*8r4YL`oXMi&4=QBikB;dZ;m);hnw}kr-;1 zh#V#CCqyJ=1%}qaLD{JDHDS-#$CM>$Q0dj=07UZ~-fAS{f5^ZL`_|*Y*+mf@wq;cE z@L@wl`ocH1isDMWg7GO|ts8BCNst zhls9IMOdQfiN&KtbanJ3(uf$eq$VNRnnrbAav%U_?*YZLPo1q zic!Kj=erBcoO7omc^{nBu08Cq2Xa>qn0exg{(eMxznBOUuosI#^r*lIFou}TWL{-5 z)ZNe?b3oo)3Iy6;79?*45UPIyKxj+YZ$KWrRdV8@c%00ESf&N-w*qwtXmrK%ZjSkm zri&A!NIQCm=TLU&j2HGY5k6Wt8-O*1!rZqA*fb6ldxa`213{M~E3UYW9fe~id=D}& z+NwIcVhmcpdmEiaq*wHQR}3>&h%S&f1nxMTJYcJ!Wk@5V(-t7|#$aMGrsHPtEX|GF z7!=4=_8XMn@4*yw5~2M?G%0SMu&)9;$j76xArOvAVV@`*i2_Nl<9ZUyv7E{>0ps|E zXfT9jLe_*V%!&9Dt1b3tiO97gb_FM;AqIRWmiXk{$$(ak1tsiip@@6Z>X(s565gGN zwiKzRux8=(qBwqW|PZb&;b9}&lJfv?lj9vTJ zO*=UNMEjwKW-h|nT|_{{khr(gAHTSg9widwm}XWfe}+MEBbGL*B@0)TNGuky*}~-! ziCLi3#BuNXD(66JP`iQRDLac#UZExrN)rBxdtCNdzU_7rQE*%sq zL4pD(9PP?=pWj5y_-%bL6NsJs%!;Eob#a2iAk9mn zYEI?>kyXnfE7n>N-YDnCRFo8RJnXVnB+#hGk?tPhQM-`BDdj}}ZXm2ZvgF@Eeal9; zMgII&q}as)y-SnvASZj>xAIa4R=R@~itF8pm0^;VkC^@+(JPiRb7MD$yzYKX z05bNS#iCaN;93->3e5YDlZ?HPA)@zh%jon|bTPD@4aJBqS8IQ3N7J|k0Yk&j6=G8< z_tR?v`{`n}pZ?C=Pp?(`>F;bh96{9dOA(zgJpC;xSNTpR3^E^T7iSSpNG86{}i(OT*yP@q`f-*G8 zf%Mk-jx2~eM*F;776a~_-$aoWy+6nBV?Zd<(6%hV{;#7H9>4Re4il8@_}zY~wQp~d zX9?@}LW z6a277Y_YWIhmGS1S+ryz%uvpr%~KQEAUaSGM#2QvC>V!n-M1oLfb!>5i0B!VI~6j& z6rPCTt5%7~Z-n#r!Ziy6FT{MhR#Smu86eCgnj&!ofgX0_19&UtXEbrJzc9=Y#X0PU zr0q}|ZAZB3MNh4SHy;C!?}LbQ_ZcsV1mH#)>v7J4fmGzhTHBF;iF`>@y85k}^Dmc&KTav}zz|hwbeNUd=+FL<$PpPF^;UH6RyS2nED| z9eC93;0zuB^0Xupo!P+|!O;D4z8TsL* zE8%gVz|Y%Q2(dxIj&}ya-{J~3dQNMx7}7r2Tm&z_C{jYEeGpEIT)h2dgiBIRPsA9U z5+HT4i(WT3$!tmMhX#lTeMc_CG}G=-*#;b&Lyp!CF_BQv0J3{1@gTunSaq7k2qo|~ z7I=jAkrenL68MtU&s_T3SpKBMTDxM%JimfgeYpxUKffU}1q)gXp_dt$F(h!WLhdLn zVk;6IS|C2<)hgBxzodEV(&&iZiVcc(9gHoEcb7e|I~naDqo4@Gl8)wsP)JN$YKoV% zOYKP7>moX0^=#yA7w7qu{*hghKheG@gvE?-$i?Q1&3{m)?eP*vrRI)`+B&U$77jk|-;mo#wLQ!L%SFu{bM+fj^CZI6LGT&FK{TbhZ<&}9X*}#{2N`dru&6`g%L7{ z=Das^*qb@*eF=wu{ckfE7$qJiLc0XKy}>&XOqJAtQ338l>99yQ?&VVnCGpsRJj8M%D>7j?ae9xS5f zh-jR3Q2s!?p+L$NRR@rZBDOff78`ELGm3efjZ!aoiK2!bz)v-7BMnZP!cxQ=Z@3=o z@iO@A?6hGRLVkhF#?a;>dUrLZ37qA?PpgiT%tPKLsablvb~Ti(h2w}zIQ~Ue?tne@ zM_xSxcZKj52?$xf14(#{soxFgPWoNM`i*dK3INCPLYvIK)*f+>N?1@Ca5;4*V$F0- z6Erbi=84v3lehoo%(!?Wir97%6EHC}FLSs4*1XWF-}!eCa~AJ|p*Am8gP_{VFI8KA zsO`-E?;ii(aF2JrOuYU@;~^I_^|${3Q}jLsM+m>i zBN8ho1*0(I2KjC`Iz~(Q*PMsk_#_z2VxT_FA0L@@D;8Z!M7P^{h3Okfd0ZEqI`_;FT~bBQ!V08_+WZ938R??d`8dC)(2dn3 zBM#>5&}isHVXq`rmSZlp#aNs0Dy2AC4Anf1kZFD+EvDKvbY>kmqZo+2e-@S@FYJFb zz#BL>?nnnS*DWM8%4Cru<0t{yEa?-P#T#^4+QeMgv*VI6Y)7q*-UU7?lizD_T$>_r z08CaxTM5$lbB9%Mc$?n=+tE2E*0TFRVm5w*{ z3#bmyCr#j`g^hi{S||p<5yBgl{2cjkQr88yVW1ei6AHVYLTEJ7ehSAi zTEt=a-27w_y-7r6ikQW+MnQ*mlF@ikV&KM67m7m-F{mUbi0GZD1k4JzgGi0`y^VhP z!aOfo#v-z&Y%^TqAV5S?sG^fABz#(Xq*v2L62-Z8*du6>#fekVoP)PL@-(G4jCc$9 z=j#k2u9#jt_Lf&v#$h7*U?X%uOe_;|xDo&(>x}`zD3?!%V>xf2i+H_DLULoYeKeTeyxm_ND!shWoSl%U2d$Vjx3gl+|#6%U2S{%Qx9L)}q#A(T^VTlt;Ts zqGU;=xsR4 zh=pV_D8a$C`4nZ+v7bX93P0rXWQSy%3G&n#x7Q#6HtEnBT~|YX?_>b5`03y+JEnkZ zF(QJVxRu9MkmIL`46;6E@m#vIxr#P~OaD{d6H^Lyj3Eu{K1w#pIP8v+1QFr6cv_ZgbeyLDi>HHMAIl>H z^y0m_!2LEpGxG#|H26qvIs5g zFuxP(d_W`(0t8t_M;En-aOAyOWiigL(wD3FNQ*Wjd-I?8EVW#;TkPsb_iD`u(Wo^ zcjI8`JG?-|vPXNQP(M6=IMkn1>d=zF1^uiC&GON7g>v=)!Hx zWT_y$DGt{P5OlR=_6pi^X+=cAZmeQu;XU1GtWCJur9FW+3+dWxwq#L&0~8c-BM2qz zE{sEawfioT?K%uPM0*cgyk6QPH#JG|8gS`CJ7Fh@w1;nM#6g)$jHK6%>ru4GiJjKF zBqv2S%Hq3XNyp;Zbs39UL9!w$q-%Iz=Sq3SjPCPxE?IUP7~MTC@=PHF>!;4^#Ti*Gh-2W z!S>Bdps@iMY7u2ubjuO*f^%i&R2z+#qA10bi0-4sgt4%(MNMMDf6$Zk1Y46L%>h!^ z8%69Y(1wwrri_a=SY%Lm3yN(5{^AbUOJXSU$VTH6mW!;(v6al}FKwuNI4mXCRQ5>7 zGMvUNk|cU7%Co zT!|#(GB~e*7J|V`d`7ME0cfbBJd~glxIj!n3Af+jRuS4-)(i$kZ``qp*pEg3JD4Ko zZ|zN6aV}3s4|oT77N+P#bci&5giJYN5Z+f!+_2QBy#1&l>~IqQFf!-sDp1A4A~+2^KCD zWcd-WXenf|j<8ses1x3H#mLJLN9I;28J~=y+!^m|v8(KkH#pkoBnv`s52FJuBifxI zgA|A1Z)>P z!0oODXArNv)L$B8eNx_}462QrGTvQOZ1ec4K$?``_27H5P+wIo0Wi0vK3~0VG;<*%*t&u;K78lP{If)wWRlV?~eC%+)WotGbw0W()JDJ?av$d)&Ik>5imROF^9 z&B;ldq_jv@0^9K{w9E$d=keB-R`ayjGG$-?qI!?5s+=sWY_4fOWO{WAYGKOtfO^Vv z>&iX0Iv={ygm^L06+>%FF*Uww;+3Q)2T9GJ##0)Y?EKWU?2IBc*Iti*YH2k~zrb7N z_ed2$Z1M>n2kd$4yhJTPVSHYOTT+=cC3jp#0g&Apc^UWzfZ=F7594!lrwpxVs4b%= zwp^M*x2LwMtj<$bBi)A_lA&y>uJ)AM&sxK{TYR6g;Vlv)ulc^6l(K&d@!Kks6@*)JDqF?{8iM{G}tnf zymI!R6G$y|6T=Pu%F^2MYD^jN z2`{X!tDO^vB4beKs;;W3qN$RD<>wU?rDqh%9InG9Q*zmMrrN z3Ei2Krl#g(rn?J@-RY@CsW$da8k>WzG$}T(g4}5c2JEwy@N`0v4w5-3vz3d340DKJ z!=}cSU~ovfs6NzIi0BL*)RI*NLUImfpS!FhmZ7Qf~#GITlB*X+Vf{ z(%V?*J!n3fx%Ih)X^g=g6Q+Y#8z3Jr*KBdftS}SjEw+|u9C;gppJoUD8 zUS4c4`vz$O9*9vp$78Fg^7?2osIT+ca_Z)!Vx_BZ@KwT*6c1icnWqZQm*#~CzUnek z1bHI@K#dJnTi@XKK{VeYUl}w82Zq_g$;#?#YG6c*eA0{p>dzs&g>BD);9h?zt*m)< z6%}N$RdqF`U>pP$Kn5pRY-&w5l4#BGCzr0iI!1I(VfgUZ&1!+^$l|Jj?Tz*@HRkA|3B`-^)Z|2W$%jB>|YzKozYQ8PgOc4rm z$v%1MH*^cy84Vd$oui^!DsjsV-Qy>la+F`UZ0uE1Xs3*e?1n0bbWXeUEd6SSX z^P&_8H?%tWtB_z&S9dT$D$IrUC}Cz{DBY$No2YX%C)PcXdm)_6=dbfB-}F^gnP?n> zTCJ_5!J#alnHRK*3i%lsY*|I#Qp7?(G|3K~#7lo+ZbfzJ9J%JPwLw*7i^{4YWSy^a zfxFUQhj0lAOvAI1F3QZ!7@wI_WGgK%t%ubH<|ngYa&Bs2HjAY)YO+qQLy1TkNlukv z(c+{O!mA?Z=@b;mwF@b<@^WPJl1^+iCyfzPFn=J#0smhLN(ZH%es)x}~7Q<^MVt$TXk>w1QdwgYnut6Cwn?qHVY0&@` z5f!he1_ouTFZWcJE`nTTl}MG!<)oU%TbP^3X}UBnwewIOj!CBVK?Wz%i?>YLRUqTx z5lvE@l+Y|NKky#xRjhe8{F<1mv*pfSIFZ9Gu~Uz<#g+%Ayngaz7KrDg)EYZz3^LEc zqJqpx6J)kX4vHZt$%DNjzlU#AP?hTNVm`2D)^IHtT#yBq*EP(p_Smq~85*#W^qjP! z9AZzd12#;qx!BS2DPy`1NjmLcXm#b((u1JmHCK+H8q!MAaxxNww9^=nMC9FGX?J9Y zT(6dvU4i`0tqj+v*1;rOMRgr)1M}Uw8G2e!<@3ming zAYK=-4uBJkPtlsDgHyypIxpZb&mo1?u9kJFD9(a|ra+qm*g_F40klq1LR^rMlb4!~ z$Wo3J(?=Pjuc1cTE{uXKxUgY%CblA+rP*rL^5wJn$kaHGrX@=5HQt7bo)~Ln8OX7Lxst zjwK)*g?>73Fk>%vKNMm3=*c!d=!+aDFI|b#)4cvv%&ONvj{m0dU&<{xPn1cMnF<7w zR`7wLF(fOGta3bU)1+xLj8ZVC#)TQZ{4E6>`265ULd<+ z#p8I2)J(c8Ym4>9O1UH)fIaEc*2#tYsbdLnYA(&hmE~w6;AfPgy7}k>NlqZ<=du)> z$lKJWIbCK>=GmATdpTTiN6KNTmIQic+Hb|WnrW)_M~+2ZWbE;HDDk5e)>d1`B2ivK zLdzU@!lWs2X-U_`B_$;cOGu0xT;p%3^`y-4;2fu_4AoL^S!K$?;nxnnc39ltIdOyY zhQ$#cL!op&(J63!OPhBAb= zO0yZ66B^1YEcI4Sh1jR`o`>o6*$Vk&#!See6`Z!=h>W*!zG>NMtPji}21n*0<47TR z4erw5=A0@Z>btAxOh5%#oGb)Xj&#COPO_AP0k$xgluy>i8ct6`ID=7B9(46koR$%{ zNXnJ2X%f}G5a)puw#;cnzEPA~P?U%)!r#iWAceVa3*u5@667sCjp{-EWIf2EtY0mi zMkQu!mwf0m+*a9;bS()XS3p0fX;u&89fz+HD+8Yld2w_^M+r8$aitZ*7ulzFL z!&=2=1`D7xloD{cm`Kx>84fRLV2d+PsMAe+SiPDyjYzgL+>`Q(#^+6$gro7yOi3r8 z@*HzxBoA#CTtWcLy!B*SR%!BR0jb9BvZ~Za%Tjt89|x1VDP;gB!4QjWNxhsH_H*7H z73Qin7i6T`Svx#mwQ3uSI6}Mkpb=BbVV-i~FqJA4Pt)>p^K&xzOpdlM(D)AA-2u@Hf+J=P@^10*Z3N}dFn(jcM1m^fA?zEHw3iH!3odvN%S zcviMZC27`N+_I3AVcFw10@Pnk4pF1@mX?i%+VWCbD}$T8^fYyL<2PMG9ZgXPGKIl< zkJBK`03Sxuv5R!2R;g@N1)uR?KI;$^XjIFJg1ttt`!wP1EseF0CG-=|jxld4!>6w(uW^xA@5=2n@ z8#Uz_li;*fejMKWpd)oc2>MM&(gyo)&g5RW)&z#>RzUcr|l0rAq)(x#-@hE&$o zc!reK%=gMag&u!?dEF4?9l1m3sJ;9*F|el{LYgV9s!b^SP0W9bmTgFb&pQNrrs{@r z&k&>l?rVn)MrdeQICxHN!w_EE7^S*u_K-PcWtXBPC7Ip-4O&BJW_`cOHtFp*5lm(& z3;s<+I)WL(vXBWVjRi|`p4=6PkuV2Oc|xU)xx>@vFE960aKVeSO^(-;<`W9PzZ~qt zS-l6D5Q!7SQg10bO1zB;*rd6X)wlUn{7f`+Q_(mQ)c5uQG?3)1}8OeHR-0ILlY8*C0yNKHSq87 zN8wi*>9-w`C(_T6w&?V$jrfl~{Ckmj+85)WKwTJR?-mj2Xu|JDQlIXGfRFsQoFyz# zf?C1S6>O<`QeX35o%|0AtXh?0)pH`AOI3L%J;ytvZVwsn?0lP+;f!0SXE^(<73t2z z6-K&q__ELm&iarAVb0;H&csw_zj4mEan8=;oKfSPj?{2lXLNs6(HcX~3}@7ekOn9H zYGxn&_Zh+PSww#@&f1W4XXjT z;jIhFaCTm+r90zR=;_XW%ftld*pLOziQ}C413BHgElGp&j0 z_dx%s#$6YJ*-`wX2k}oDY{ha`c*yTsLnL2!C|XaqL5p<(F4~~=pBK|gM*mZaRx!y4 z{oh7>trNJxou6=tLp;=19=XR#{oG5$R$9=usQheJH^ND03Z2*d1pR?^FG13RA>`I+8l#a zWQ)Up={o-4^8o#s2#=Z^=5$O5UyQ#QghRGgsc>eidAL1f0*yJIY%Mru8u@v;bE$n^ znFBRy4}=A=qIBGv|ro+WQKYO-O!c)KnNg;eMy!c?8EN4*xm$$8T-6xdfO< zK0MPIcT*VqIKm6dFi7G2L^wQuFqhjjL?RWdf_f`Jta<@5v>B^t4i+j}Ptb4cGA)jK z?0Z3dsJPc+*K`-n8HJlnAvcXvizO$W`sldv$R4~R^{Fg3& z71{PF%EYsZF|doZI%3}nTEmtZ*$ANr!#IAXwqCo4#wtbQvHvBFm2J`>yMI;DcwEu2 z#$c?a%S4tlKV&rm9mcn=8|0r32X(U9K4`70wqnzG%;Dk7=!)&3NYQ$zH7)iN)_ZVG zAMc!nf9A7IV(H-|Q?sJ=-;wG4|3R7hsU*mIIhlgKKOS1Xtxb^-NG3uizoHdMw6X&0 z0M?{+`b6?otd%Pa6$|r24uRIC>+ZO4+bnc%QMC34_(EP`UBg+|iq(mfM51W zj(Z%Zm|x}JV<}Wz8bWDNAzJ$tt--BnvCT5AY|^9h@3HPS)kp!4XgMhG;Zsd9jOT>) znAduJQheoy6tzhVb3*_AM3JI}-|8m#W^kV1I5<_*vg}A3OO-+Vq4Qr`Nxxr zeiq6!AD85c`Od^yt-YLO{8IJ%pYY4!5j42WC!|e4Mr9M6S79EP8I`RK0Qlt7e%1E} zBEbB^2CTVmK0@V|i{ca}o%-%keRon{sUdtR_4iwbsEE z1)k&JUL&|3@H&-^93Yfh1B~V0J8wRK_P|w+y)kMzE(Yv?x`B(vKGSht6BKX*RWBwFZNZx#MizpxKne zw*~`WF@FiX{Y4O2)odLu12;3uU^D3`+9H?98SRZpmluDA06_R=n~6Uba1&w6DN$pX1ACIvqW)nP0 z<@vm42mFr;K1=1gy#EILB?XUDF2;Llz)vXnDFx?!G2s7D@UhBZDc14_dZ%%hDmd?X z(a=G`$A(EX-qWHO2Y6fhAAA{j%4Oi$mw{goIPp`jVma?&F>d8$@aq+RoC*aGNOo;Y zE`z`FGVt|^&QV2&_pzYWd>Q=5F9Sbt8Teb5fxizp$vaksEHgrVuHds2+?=l;6ueo% zvz5HM0R<--zPx6?|473Fkc^iaP;s zi=SsN1OKz46L+Pg!+Sgw-%;?Wt0ep}`LKPe;4KRNxPo7}41EXuoc*hxMCAP!id_J2 zi_hK)f0hbhoXY`!n1V+sg_+~#D)>*1?QX;Z5rY%$C(s~xKrV~6g&(PjRI;*87SmtO`>cFlG&Ueckwgg&<_c$0z$^0(ld5R_V9 z*yGEqtwDs->H>|#X$7JeV8IKvTr=+iQzdwV4rui4A$3)o-;}kwvvA3cGU_hx(=al> zXW`2gbSo&(NSBMLqAvAWP^;MPjCYRww%mdpcxx;%c#ObpW}tpwXoV;Eu&Pq*v zQ7=eD0q`!|yn#s_QMJBxSFg!cOW?i%uyFTJLDidiRWtAKn_pHUaB$F|fk{ae6BYp& zBiP~6?|Sh#7u`)zU*e&rOYRr8ako>UtJ^x%>ynG!7Y(4e;_pJWx)dS9w`~pbZu(#u zvQg@E{(}6xV1ZN0X~I9V!08=_2^Yxw=tFd9PiVqpF9SF8^^5T{E%;q5bndpmDP=S1 z|H}fm;txc=Lm#4V#m}_Bd!WsvbH4>nDV_=c;xcf`r|3iUd*MG5zdZr)xfo9Q6n!Xd z>W%+Q{3|T@@fLWI1>VO3UuJ<@^}ovkx5_mc>oa|**{Ywb3D?a4UYOBRH-VKU(eYE~dZ30=ME1Q2V}%@t0ZPgE5q;=N1dRj|DzZB{D>Z zQc)9smIXe<0zYJdQ;KTh$48)o&&70-EpRKHnHIQp9`{?|LqX4^-(4Ls5&c0Hcmz2o zJ{QBgTHsdtr55-w^fT$VSm0LtRFzZ`{p&3FO%}M7&lfFl+7p`e|6+lUu)rhKzK`gS zw7?TAa4SD2EpSSKP5R5#e(+*C4_n|?I{&o5t#b8N`$wW<RTj9F&R;F?ZWjEmolwDt z=%nF4lTIW#8$JXlQA~K61x_Yo!UuIl1)q!Y-4?i250i-rh2I}flg?2K+{#aw+IJKE zffoFp7PwU|_hsM@THxa>bgrhLi4W1Y(phPNTlsm)0=L@F#}>HNetxvTuf!Opyz%PX zfX21j&p->@N+-nvr!BroXNm=G#lO)4r!BsTf0G4n9e0}ro@&8=!2-AH;iv^}o!|Ru zQN)Mjvg+rU1#ac@pBDHS^fCDijY9>Wi}lkjj=`7Ezup43$~D^pw~m{8MUalwAMdij zt$uXc0=Lrn!2%x(k|zIsXtBqK__yjU%L2F3udu+a^zXO~e1`=-7Gs#>K5v0r_3(oQ zZuRSg`2UN!^MJ3aTHf|P>4k*QI~;nE-b5^f9ux#=f+7+~fM}W_p^1v92qISyP;7u8 zV#R_Ab`TU53-*E{7VL@@vGUEV^-eMw$^Coph48Ika?W1Qp8c*_b+26yZA^jv)cyK+ z#dW_JuDJGdn&R62DaCcVzbUTM9n!?P>c*xed;l#*Zm>}=igXg*H;V0Po;I@XR6}5{Bso7>F!W` zkkV<&gD(Y6cL@E9{#3=aKmSr(>pZKt_VceYc;i-_s!vzz4^dqEbB5wNUuzZD{_Iy= z>zu$l52UO4zZBR0+^V?N-=Vm+KcKkQkKjog($)U=Qe3x(k&0{m>5A)dW0B&zJv^?s zPPa~*5`Tu$qSSkl;<`OQqqz2Gx8ho-CQqg*aJ$leo>~T(V`i7A0Tn6d$JejP@n=!xg`>41Q-Bd`lVpgEIJ!W$#q;v*E_sJQmO zYDY4nz&e`emcdW#&z~LFD--LRtA5n4E|;r{2Rq}`JB-C(Eb-HuKk~1 z247bOf3ghzRvCO>89cU2iJ!x1QQBvnGI+Z(c<(a!8H(%j`A~74?yqI=N?i}_XUj5p z&ocOkGI&-Q{DLz0QpI(?VtCM`!0mhl{Y!bCsJPDW$TIlSGWdov`0g_JPi64_Jg8By z{d&3L+Rs5eh{IlwV>c^4jMhtjpDu&<;lY;zr>k|&R9x#ksJI@_V~%$+PDhW!1Iyqm z6xZYVLyBwtU5aaa`VDCp*ngdFWyPz~Iw^;~itGH|Tn68)xb~;QiHA;iR2h7{;@Y1l z6xaG+DX#aAHBTx@SL=*YoHv#H9>WJq6u5kNS1deF@g|C|QhdDPZ>3U%0_$j=*VEy@ z>2FkA>xX-l*z+!0{JB|i?dRu;Yn^1SYzo$&#(he-*1x0-UbAnBeFiOxKcf}rU9s@R zif1Zbq2Hl(qCIwgR#5iU6i-(CmeP)zmFOo1^-_%JU!Nf4-J0<{42xzApVQtbQ6dRd|xC7DbIK7 z_;@A5JE9#nGyG-f9Ah}&pT?n^;hn+z8XgNS_YsOe?O>N-?B}9A@coh;#C`-XP`J?W zy%=ERI;q(Ih;)}5`_ItH)*3$D%Ln%x{w@4@((r7Q+Y5&Og>w7Y@MQS2&+tnzaQ<%i zIoLtf#CDReT=cJIhOb7umHn64cZZ+H8v973*W2*T=ofSiH-=Y4ef?y3CQmvj{Azd|?C4@J-;i`WqCBe_E_PwVd!s!pG5lBbmzxd03;t|0 zyc+y_-0KLh3Vvf-~I{)XXQFn;bb{6dt&hlc-zcK(Cmb5Z^W3_l+2 z_AkR5!++WTNcnH#ogsw;)U)sf@TZR9BVjN14~Tsd+GlfP-w=MZF?=BGI~l$JaO=BTgP zh93Z*Yxsm(K7oaX|BCoh!{vJUI>T$BU&#F%Qjch!!3Jah3hL`&!;`Szf70-o5aRn% zIf(vwXjg9;j^QeJ-|)Mzzu@~=wN6hKr0|8|OCYq*@Kex!em1-X#^G4hkN9~N?2-(B z0i5rLAO<#vbRXTYEP48IleO@?=qeq?w*@MjEv z6#eT(!^gt@b;BPw83@-g%(tQyAw=?!H`eqz-HGB)&=ZS`gkxzLyfapiU z?i6GH7V1&vp<>?={b;nYZ-#l4JohB_m!q9eGWJc8-(15V!#Go9_>184443w4{3fKk&2Sk9 zcNqQ!7dQnue<0=G7xga3Rl+a8_HEH#gdY$4lMU~S{lzH5-$yx2H2h5DtI+W8VK4g) zN%s+qV~dP^BKTUv>tTQWgy9!soZMk}9`g0J;jf|I_ZogD#>pQH?|}Yz(C`Sf&k86f z$=7`Bk7^ly2l~-?!8JMfi;|Ah8)x8Yx5fApx~Z)042+3*|CU%oT^89!_Ue;7Uh`HJHT zp&<40KE|G&~P}UT%0A_)^2C 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%0Dits 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 diff --git a/protos/cmvr/api/agv_command.proto b/protos/cmvr/api/agv_command.proto deleted file mode 100644 index 8cfe89a1..00000000 --- a/protos/cmvr/api/agv_command.proto +++ /dev/null @@ -1,906 +0,0 @@ -/** - * @file agv_command.proto - * @brief AGV 控制相关的 Protobuf 消息定义。 - * 涵盖仙工智能(Seer)SRC 系列控制器的主要 API 命令, - * 包括状态查询、电池、位置、地图管理、控制权、运动控制、导航任务等。 - * @note 所有命令均遵循仙工协议帧格式,通过 gRPC 服务(AgvService)对外暴露。 - */ - -syntax = "proto3"; - -import "cmvr/api/common.proto"; - -package cmvr.api; - -// ============================================================================ -// 1. 基本信息查询(命令码 1000, 0x03E8) -// ============================================================================ - -/** - * @brief AGV 基本信息响应结构。 - * @note 对应仙工 API 1000(0x03E8)的响应体,包含设备标识、软硬件版本、网络状态等。 - */ -message AgvStatusInfo { - optional string id = 1; ///< AGV 设备唯一 ID - optional string vehicle_id = 2; ///< 车辆编号(如 "agv_001") - optional string version = 3; ///< 软件版本号 - optional string model = 4; ///< 车型型号(如 "SRC-1100") - optional string dsp_version = 5; ///< DSP 固件版本 - optional string current_ip = 6; ///< 当前 IP 地址 - optional string mac = 7; ///< MAC 地址 - optional int32 rssi = 8; ///< Wi-Fi 信号强度(0~100) - optional int32 ret_code = 9; ///< 返回码,0 表示成功 - optional string err_msg = 10; ///< 错误信息(失败时填充) -} - -/** - * @brief 获取 AGV 状态命令(请求/反馈)。 - * @note 请求携带设备 ID(header.device_id),反馈返回 AgvStatusInfo。 - */ -message GetAgvStatusInfoCommand { - message Request { - CommandHeader.Request header = 1; ///< 公共请求头,包含 device_id - } - - message Feedback { - CommandHeader.Feedback header = 1; ///< 公共响应头(success/error_message/timestamp) - AgvStatusInfo status = 2; ///< AGV 状态信息 - } -} - - -// ============================================================================ -// 2. 电池状态查询(命令码 1007, 0x03EF) -// ============================================================================ - -/** - * @brief 电池状态响应结构。 - * @note 对应仙工 API 1007(0x03EF),支持简单/全量两种模式。 - */ -message AgvBatteryStatus { - optional double battery_level = 1; ///< 电池电量(0~1,对应 0%~100%) - optional double battery_temp = 2; ///< 电池温度(摄氏度) - optional bool charging = 3; ///< 是否正在充电 - optional double voltage = 4; ///< 当前电压(V) - optional double current = 5; ///< 当前电流(A) - optional double max_charge_voltage = 6; ///< 最大充电电压(-1 表示不支持) - optional double max_charge_current = 7; ///< 最大充电电流(-1 表示不支持) - optional bool manual_charge = 8; ///< 是否连接手动充电器(仅 SRC-2000) - optional bool auto_charge = 9; ///< 是否连接自动充电桩(仅 SRC-2000) - optional int32 battery_cycle = 10; ///< 电池循环次数(从 BMS 读取,可能不准) - optional string battery_user_data = 11; ///< 电池用户自定义数据 - optional string extra = 12; ///< 扩展字段(预留) - optional int32 ret_code = 13; ///< 返回码,0 表示成功 - optional string create_on = 14; ///< 数据创建时间戳(ISO 8601) - optional string err_msg = 15; ///< 错误信息 -} - -/** - * @brief 电池状态请求数据。 - */ -message RobotStatusBatteryRequestData { - optional bool simple = 1; ///< true=仅关键数据,false=全量数据(默认 false) -} - -/** - * @brief 电池状态查询命令。 - */ -message RobotStatusBatteryCommand { - message Request { - CommandHeader.Request header = 1; ///< 公共请求头 - RobotStatusBatteryRequestData data = 2; ///< 请求参数 - } - message Feedback { - CommandHeader.Feedback header = 1; ///< 公共响应头 - AgvBatteryStatus status = 2; ///< 电池状态信息 - } -} - - -// ============================================================================ -// 3. 机器人位置查询(命令码 1004, 0x03EC) -// ============================================================================ - -/** - * @brief 机器人位置响应结构。 - * @note 对应仙工 API 1004(0x03EC),返回世界坐标系下的位姿。 - */ -message AgvRobotLocation { - optional double x = 1; ///< X 坐标(米) - optional double y = 2; ///< Y 坐标(米) - optional double angle = 3; ///< 朝向角(弧度),逆时针为正 - optional double confidence = 4; ///< 定位置信度(0~1) - optional string current_station = 5; ///< 当前所在站点 ID(若在站点上) - optional string last_station = 6; ///< 上一个经过的站点 ID - optional int32 loc_method = 7; ///< 定位方式(0=自然轮廓, 1=反光柱, 2=二维码, 3=里程计...) - optional int32 ret_code = 8; ///< 返回码,0 表示成功 - optional string create_on = 9; ///< 时间戳 - optional string err_msg = 10; ///< 错误信息 -} - -/** - * @brief 位置查询命令(无请求数据)。 - */ -message RobotStatusLocCommand { - message Request { - CommandHeader.Request header = 1; ///< 公共请求头 - } - message Feedback { - CommandHeader.Feedback header = 1; ///< 公共响应头 - AgvRobotLocation status = 2; ///< 位置信息 - } -} - - -// ============================================================================ -// 4. 地图下载(命令码 4011, 0x0FAB) -// ============================================================================ - -/** - * @brief 地图下载请求数据。 - */ -message RobotConfigDownloadMapRequestData { - optional string map_name = 1; ///< 要下载的地图名称(必填) -} - -/** - * @brief 地图下载响应结果。 - */ -message AgvDownloadMapResult { - optional int32 ret_code = 1; ///< 返回码,0 表示成功 - optional string create_on = 2; ///< 时间戳 - optional string err_msg = 3; ///< 错误信息 - optional string map_content = 4; ///< 地图 JSON 文件内容(成功时填充) -} - -/** - * @brief 地图下载命令。 - */ -message RobotConfigDownloadMapCommand { - message Request { - CommandHeader.Request header = 1; - RobotConfigDownloadMapRequestData data = 2; - } - message Feedback { - CommandHeader.Feedback header = 1; - AgvDownloadMapResult status = 2; - } -} - - -// ============================================================================ -// 5. 地图状态查询(命令码 1300, 0x0514) -// ============================================================================ - -/** - * @brief 单个地图文件信息。 - */ -message MapFileInfo { - optional string name = 1; ///< 文件名 - optional string modified = 2; ///< 最后修改时间 - optional int64 size = 3; ///< 文件大小(字节) -} - -/** - * @brief 地图状态响应结构。 - * @note 对应仙工 API 1300(0x0514),返回当前地图及所有地图列表。 - */ -message AgvMapStatus { - optional string current_map = 1; ///< 当前加载的地图名称 - optional string current_map_md5 = 2; ///< 当前地图的 MD5 校验值 - repeated string maps = 3; ///< 所有地图名称列表 - repeated MapFileInfo map_files_info = 4; ///< 每个地图文件的详细信息 - optional int32 ret_code = 5; ///< 返回码,0 表示成功 - optional string create_on = 6; ///< 时间戳 - optional string err_msg = 7; ///< 错误信息 -} - -/** - * @brief 地图状态查询命令(无请求数据)。 - */ -message RobotStatusMapCommand { - message Request { - CommandHeader.Request header = 1; - } - message Feedback { - CommandHeader.Feedback header = 1; - AgvMapStatus status = 2; - } -} - - -// ============================================================================ -// 6. 上传地图(命令码 4010, 0x0FAA) -// ============================================================================ - -/** - * @brief 上传地图请求数据。 - */ -message RobotConfigUploadMapRequestData { - optional string map_content = 1; ///< 完整的地图 JSON 字符串(必填) -} - -/** - * @brief 上传地图响应结果。 - */ -message AgvUploadMapResult { - optional int32 ret_code = 1; ///< 返回码,0 表示成功 - optional string create_on = 2; ///< 时间戳 - optional string err_msg = 3; ///< 错误信息 -} - -/** - * @brief 上传地图命令。 - */ -message RobotConfigUploadMapCommand { - message Request { - CommandHeader.Request header = 1; - RobotConfigUploadMapRequestData data = 2; - } - message Feedback { - CommandHeader.Feedback header = 1; - AgvUploadMapResult status = 2; - } -} - - -// ============================================================================ -// 7. 抢占控制权(命令码 4005, 0x0FA5) -// ============================================================================ - -/** - * @brief 抢占控制权请求数据。 - */ -message RobotConfigLockRequestData { - optional string nick_name = 1; ///< 抢占者昵称/标识(必填) -} - -/** - * @brief 抢占控制权响应结果。 - */ -message AgvLockResult { - optional int32 ret_code = 1; ///< 返回码,0 表示成功 - optional string create_on = 2; ///< 时间戳 - optional string err_msg = 3; ///< 错误信息 -} - -/** - * @brief 抢占控制权命令。 - */ -message RobotConfigLockCommand { - message Request { - CommandHeader.Request header = 1; - RobotConfigLockRequestData data = 2; - } - message Feedback { - CommandHeader.Feedback header = 1; - AgvLockResult status = 2; - } -} - - -// ============================================================================ -// 8. 查询当前控制权所有者(命令码 1060, 0x0424) -// ============================================================================ - -/** - * @brief 当前控制权所有者响应结构。 - * @note 对应仙工 API 1060(0x0424)。 - */ -message AgvCurrentLockStatus { - optional bool locked = 1; ///< 是否被锁定(有持有者) - optional string ip = 2; ///< 持有者 IP 地址 - optional int32 port = 3; ///< 持有者端口 - optional uint32 type = 4; ///< 持有者类型(0=default, 2=roboshop, 0xDD=srd) - optional string nick_name = 5; ///< 持有者昵称 - optional int64 time_t = 6; ///< 锁定时间戳(Unix 秒) - optional string desc = 7; ///< 描述信息 - optional int32 ret_code = 8; ///< 返回码,0 表示成功 - optional string create_on = 9; ///< 时间戳 - optional string err_msg = 10; ///< 错误信息 -} - -/** - * @brief 查询控制权命令(无请求数据)。 - */ -message RobotStatusCurrentLockCommand { - message Request { - CommandHeader.Request header = 1; - } - message Feedback { - CommandHeader.Feedback header = 1; - AgvCurrentLockStatus status = 2; - } -} - - -// ============================================================================ -// 9. 开环速度运动控制(命令码 2010, 0x07DA) -// ============================================================================ - -/** - * @brief 开环速度运动控制请求数据。 - * @note 命令码 2010(0x07DA)。下发后会强制取消当前自动导航任务。 - * 多舵轮设备仅 vx/vy/w 生效;steer/real_steer 仅单舵轮设备有效。 - * duration = -1 表示持续运行直到下一次指令。 - */ -message RobotMotionControlRequestData { - optional double vx = 1; ///< X 方向线速度(m/s) - optional double vy = 2; ///< Y 方向线速度(m/s) - optional double w = 3; ///< 角速度(rad/s),逆时针为正 - optional double steer = 4; ///< 舵轮转向角(单舵轮,单位 rad) - optional double real_steer = 5; ///< 实际舵轮角度(单舵轮,优先级高于 steer) - optional int64 duration = 6; ///< 持续时间(ms),-1 表示一直运行 -} - -/** - * @brief 开环运动控制响应结果。 - */ -message RobotMotionControlResult { - optional int32 ret_code = 1; ///< 返回码,0 表示成功 - optional string create_on = 2; ///< 时间戳 - optional string err_msg = 3; ///< 错误信息 -} - -/** - * @brief 开环运动控制命令。 - */ -message RobotMotionControlCommand { - message Request { - CommandHeader.Request header = 1; - RobotMotionControlRequestData data = 2; - } - message Feedback { - CommandHeader.Feedback header = 1; - RobotMotionControlResult status = 2; - } -} - - -// ============================================================================ -// 10. 切换载入地图(命令码 2022, 0x07E6) -// ============================================================================ - -/** - * @brief 切换地图请求数据。 - * @note 命令码 2022(0x07E6)。目标地图必须已存在于机器人中。 - */ -message RobotLoadMapRequestData { - optional string map_name = 1; ///< 目标地图名称(必填,仅字母数字-_) -} - -/** - * @brief 切换地图响应结果。 - */ -message RobotLoadMapResult { - optional int32 ret_code = 1; ///< 返回码,0 表示成功 - optional string create_on = 2; ///< 时间戳 - optional string err_msg = 3; ///< 错误信息 -} - -/** - * @brief 切换地图命令。 - */ -message RobotLoadMapCommand { - message Request { - CommandHeader.Request header = 1; - RobotLoadMapRequestData data = 2; - } - message Feedback { - CommandHeader.Feedback header = 1; - RobotLoadMapResult status = 2; - } -} - - -// ============================================================================ -// 11. 查询地图载入状态(命令码 1022, 0x03FE) -// ============================================================================ - -/** - * @brief 地图载入状态响应。 - * @note 命令码 1022(0x03FE)。loadmap_status: 0=失败, 1=成功, 2=载入中。 - * 载入中(2)时禁止执行重定位操作。 - */ -message RobotQueryLoadMapStatusResult { - optional int32 loadmap_status = 1; ///< 0=失败, 1=成功, 2=载入中 - optional int32 ret_code = 2; ///< 返回码,0 表示成功 - optional string create_on = 3; ///< 时间戳 - optional string err_msg = 4; ///< 错误信息 -} - -/** - * @brief 查询地图载入状态命令(无请求数据)。 - */ -message RobotQueryLoadMapStatusCommand { - message Request { - CommandHeader.Request header = 1; - } - message Feedback { - CommandHeader.Feedback header = 1; - RobotQueryLoadMapStatusResult status = 2; - } -} - - -// ============================================================================ -// 12. 查询站点列表(命令码 1301, 0x0515) -// ============================================================================ - -/** - * @brief 单个站点信息。 - */ -message StationItem { - optional string id = 1; ///< 站点唯一 ID - optional string type = 2; ///< 站点类型(如 "LocationMark", "ChargePoint") - optional double x = 3; ///< X 坐标(米) - optional double y = 4; ///< Y 坐标(米) - optional double r = 5; ///< 朝向角(弧度) - optional string desc = 6; ///< 描述信息 - optional string executor = 7; ///< 执行器信息 - optional string prepoint = 8; ///< 前置点 ID - optional string recfile = 9; ///< 识别文件路径 - optional bool spin = 10; ///< 是否允许自旋 - optional bool use_down_pgv = 11; ///< 是否使用下视 PGV -} - -/** - * @brief 站点列表响应结果。 - */ -message QueryStationListResult { - repeated StationItem stations = 1; ///< 站点列表 - optional int32 ret_code = 2; ///< 返回码,0 表示成功 - optional string create_on = 3; ///< 时间戳 - optional string err_msg = 4; ///< 错误信息 -} - -/** - * @brief 查询站点列表命令(无请求数据)。 - */ -message QueryStationListCommand { - message Request { - CommandHeader.Request header = 1; - } - message Feedback { - CommandHeader.Feedback header = 1; - QueryStationListResult status = 2; - } -} - - -// ============================================================================ -// 13. 指定路径导航(命令码 3066, 0x0BFA) -// ============================================================================ - -/** - * @brief 单段导航任务单元。 - * @note 用于 3066 命令的 move_task_list 数组。 - * source_id 和 id 之间必须有直接路径,不可跳点。 - */ -message MoveTaskItem { - optional string task_id = 1; ///< 任务唯一 ID(必填) - optional string source_id = 2; ///< 起始站点 ID(必填) - optional string id = 3; ///< 目标站点 ID(必填) - optional string operation = 4; ///< 到达后执行的动作(如 "JackLoad") - optional double jack_height = 5; ///< 顶升高度(米) -} - -/** - * @brief 指定路径导航请求数据。 - */ -message RobotGoTargetListRequestData { - repeated MoveTaskItem move_task_list = 1; ///< 导航任务序列(顺序执行) -} - -/** - * @brief 指定路径导航响应结果。 - * @note ret_code=0 仅表示指令被接收,不表示执行完成。 - */ -message RobotGoTargetListResult { - optional int32 ret_code = 1; ///< 返回码,0 表示成功接收 - optional string create_on = 2; ///< 时间戳 - optional string err_msg = 3; ///< 错误信息 -} - -/** - * @brief 指定路径导航命令。 - */ -message RobotGoTargetListCommand { - message Request { - CommandHeader.Request header = 1; - RobotGoTargetListRequestData data = 2; - } - message Feedback { - CommandHeader.Feedback header = 1; - RobotGoTargetListResult status = 2; - } -} - - -// ============================================================================ -// 14. 查询当前实时导航状态(命令码 1020, 0x03FC) -// ============================================================================ - -/** - * @brief 1020 命令请求数据。 - */ -message RobotStatusTaskReqData { - optional bool simple = 1; ///< true=仅返回 task_status,false=全量 -} - -/** - * @brief 导航任务中的料箱信息。 - */ -message NavContainerItem { - optional string container_name = 1; ///< 料箱名称 - optional string desc = 2; ///< 描述 - optional string goods_id = 3; ///< 绑定的货物 ID - optional bool has_goods = 4; ///< 是否已绑定货物 -} - -/** - * @brief 1020 命令响应数据。 - */ -message RobotStatusTaskResData { - optional int32 task_status = 1; ///< 0=NONE, 1=WAITING, 2=RUNNING, 3=SUSPENDED, 4=COMPLETED, 5=FAILED, 6=CANCELED - optional int32 task_type = 2; ///< 0=无, 1=自由导航到坐标, 2=自由导航到站点, 3=路径导航到站点, 7=平动转动 - optional string target_id = 3; ///< 目标站点 ID(task_type 为 2/3 时) - repeated double target_point = 4; ///< 目标坐标 [x, y, r](task_type 为 1 时) - repeated string finished_path = 5; ///< 已经过的站点列表 - repeated string unfinished_path = 6; ///< 尚未经过的站点列表 - optional string move_status_info = 7; ///< 附加信息(如错误描述) - repeated NavContainerItem containers = 8; ///< 料箱信息 - optional int32 ret_code = 9; ///< 返回码,0 表示成功 - optional string create_on = 10; ///< 时间戳 - optional string err_msg = 11; ///< 错误信息 -} - -/** - * @brief 查询当前导航状态命令。 - */ -message RobotStatusTaskCurrentCommand { - message Request { - CommandHeader.Request header = 1; - RobotStatusTaskReqData data = 2; - } - message Feedback { - CommandHeader.Feedback header = 1; - RobotStatusTaskResData data = 2; - } -} - - -// ============================================================================ -// 15. 批量查询任务状态(命令码 1110, 0x0456) -// ============================================================================ - -/** - * @brief 1110 命令请求数据。 - */ -message QueryTaskStatusPackageReqData { - repeated string task_ids = 1; ///< 要查询的任务 ID 列表;为空则查所有未完成 + 最近一条完成 -} - -/** - * @brief 单条任务状态项。 - */ -message SingleTaskStatusItem { - optional string task_id = 1; ///< 任务 ID - optional int32 status = 2; ///< 状态码(同 task_status) - optional int32 type = 3; ///< 任务类型(同 task_type) -} - -/** - * @brief 任务状态包(包含最近完成和所有未完成的任务)。 - */ -message TaskStatusPackage { - optional string closest_target = 1; ///< 最近站点 ID - optional string source_name = 2; ///< 起点站点名称 - optional string target_name = 3; ///< 终点站点名称 - optional double percentage = 4; ///< 任务进度百分比(0~100) - optional double distance = 5; ///< 剩余距离(米) - optional string info = 6; ///< 附加信息 - repeated SingleTaskStatusItem task_status_list = 7; ///< 所有任务状态列表 -} - -/** - * @brief 1110 命令响应数据。 - */ -message QueryTaskStatusPackageResData { - optional TaskStatusPackage task_status_package = 1; ///< 任务状态包 - optional int32 ret_code = 2; ///< 返回码,0 表示成功 - optional string create_on = 3; ///< 时间戳 - optional string err_msg = 4; ///< 错误信息 -} - -/** - * @brief 批量查询任务状态命令。 - */ -message RobotStatusTaskPackageCommand { - message Request { - CommandHeader.Request header = 1; - QueryTaskStatusPackageReqData data = 2; - } - message Feedback { - CommandHeader.Feedback header = 1; - QueryTaskStatusPackageResData data = 2; - } -} - - -// ============================================================================ -// 16. 单点站点自动规划导航(命令码 3051, 0x0BEB) -// ============================================================================ - -/** - * @brief DI(数字输入)点位。 - */ -message DIItem { - int32 id = 1; ///< DI 编号 - bool status = 2; ///< 状态(true=高电平, false=低电平) -} - -/** - * @brief DO(数字输出)点位。 - */ -message DOItem { - int32 id = 1; ///< DO 编号 - bool status = 2; ///< 状态(true=高电平, false=低电平) -} - -/** - * @brief 音频播放参数。 - */ -message SoundArgs { - optional string name = 1; ///< 音频文件名 - optional int32 loop = 2; ///< 循环次数(0=播放一次, 1=循环) - optional int32 stop = 3; ///< 1=停止播放 -} - -/** - * @brief WaitDI 动作参数。 - */ -message WaitDIArgs { - repeated DIItem DI = 1; ///< 需要等待的 DI 条件列表 - optional double timeout = 2; ///< 超时时间(秒),0 表示无限等待 -} - -/** - * @brief SetDO 动作参数。 - */ -message SetDOArgs { - repeated DOItem do_list = 1; ///< 要设置的 DO 列表 -} - -/** - * @brief PGV(二维码)二次定位参数。 - */ -message PgvParam { - optional bool use_pgv = 1; ///< 是否使用上视 PGV - optional bool use_down_pgv = 2; ///< 是否使用下视 PGV - optional double pgv_adjust_dist = 3; ///< 最大调整半径(米) - optional double pgv_adjust_cx = 4; ///< 二维码坐标系 X 偏移(米) - optional double pgv_adjust_cy = 5; ///< 二维码坐标系 Y 偏移(米) - optional double pgv_x_adjust = 6; ///< 仅调整 X 方向误差(米) -} - -/** - * @brief 自由导航目标点(坐标 + 朝向)。 - */ -message FreeGoPoint { - double x = 1; ///< X 坐标(米) - double y = 2; ///< Y 坐标(米) - double theta = 3; ///< 朝向角(弧度) -} - -/** - * @brief 脚本自定义参数。 - */ -message ScriptArgs { - map str_kv = 1; ///< 字符串键值对 - map num_kv = 2; ///< 数字键值对 - repeated DOItem do_list = 3; ///< DO 列表 - repeated DIItem di_list = 4; ///< DI 列表 -} - -/** - * @brief 3051 命令请求数据(单点站点自动规划导航)。 - * @warning 严禁用于多车调度场景,仅限单车测试/验证。 - * 下发此任务会取消当前正在执行的任务(不排队)。 - * 成功下发后会自动清除指定的 warning/error 报错码。 - * 自由导航(freego)仅支持双轮差速底盘。 - */ -message RobotGoTargetReqData { - // -------- 必填字段 -------- - string source_id = 1; ///< 起始站点 ID("SELF_POSITION" 表示当前位置) - string id = 2; ///< 目标站点 ID("SELF_POSITION" 表示原地执行 operation) - optional string task_id = 3; ///< 任务 ID(建议提供) - - // -------- 运动通用参数 -------- - optional double angle = 4; ///< 目标点朝向角(弧度) - optional string method = 5; ///< "forward"(正走)或 "backward"(倒走) - optional double max_speed = 6; ///< 最大线速度(m/s),0 使用默认 - optional double max_wspeed = 7; ///< 最大角速度(rad/s) - optional double max_acc = 8; ///< 最大加速度(m/s²) - optional double max_wacc = 9; ///< 最大角加速度(rad/s²) - optional int64 duration = 10; ///< 到达后等待时间(毫秒) - optional int32 orientation = 11; ///< 全向车保持的角度(暂不使用) - optional bool spin = 12; ///< 是否随动(托盘旋转) - optional int64 delay = 13; ///< 延迟结束导航状态(毫秒),缺省 0 - optional int32 start_rot_dir = 14; ///< 起步旋转方向:-1=顺时针, 0=就近, 1=逆时针 - optional int32 end_rot_dir = 15; ///< 到点旋转方向:-1=顺时针, 0=就近, 1=逆时针 - optional double reach_dist = 16; ///< 到点位置精度(米) - optional double reach_angle = 17; ///< 到点角度精度(弧度) - optional string skill_name = 18; ///< 技能名称:"Action" 或 "GotoSpecifiedPose" - - // -------- PGV -------- - optional PgvParam pgv = 19; ///< 二次定位参数 - - // -------- 设备动作 -------- - optional string operation = 20; ///< 操作类型(JackLoad/ForkUnload/RollerLoad/HookLoad/WaitDI/SetDO/sound/Script 等) - optional double jack_height = 21; ///< 顶升高度(米) - optional double start_height = 22; ///< 货叉起步前举升高度(米) - optional double end_height = 23; ///< 货叉到点后举升高度(米) - optional double fork_mid_height = 24; ///< 货叉行走过程中举升高度(米) - optional double fork_dist = 25; ///< 货叉前移距离(米) - optional string direction = 26; ///< 方向:"left"/"right"/"front"/"back" - optional bool recognize = 27; ///< 是否启用视觉识别 - optional string recfile = 28; ///< 识别文件路径(如 "shelf/s0002.shelf") - - // -------- 音频 -------- - optional SoundArgs sounds_args = 29; ///< 音频参数 - - // -------- WaitDI / SetDO -------- - optional WaitDIArgs wait_di_args = 30; ///< WaitDI 参数 - optional SetDOArgs set_do_args = 31; ///< SetDO 参数 - - // -------- 脚本 -------- - optional string script_name = 32; ///< 脚本文件名 - optional ScriptArgs script_args = 33; ///< 脚本参数 - optional int32 script_stage = 34; ///< 执行阶段:0=导航前, 1=导航中, 2=导航后, 3=脚本控制导航 - - // -------- 原地旋转(GoByOdometer) -------- - optional double move_angle = 35; ///< 旋转角度(弧度) - optional double speed_w = 36; ///< 旋转角速度(rad/s) - optional int32 loc_mode = 37; ///< 定位模式:1=激光定位, 0=里程定位 - - // -------- 自由导航(仅双轮差速) -------- - optional FreeGoPoint freego = 38; ///< 自由导航目标(若提供则忽略 id 字段) -} - -/** - * @brief 3051 命令响应数据。 - */ -message RobotGoTargetResData { - optional int32 ret_code = 1; ///< 返回码,0 表示成功接收 - optional string create_on = 2; ///< 时间戳 - optional string err_msg = 3; ///< 错误信息 -} - -/** - * @brief 单点导航命令。 - */ -message RobotGoTargetCommand { - message Request { - CommandHeader.Request header = 1; - RobotGoTargetReqData data = 2; - } - message Feedback { - CommandHeader.Feedback header = 1; - RobotGoTargetResData data = 2; - } -} - - -// ============================================================================ -// 17. 停止开环运动(命令码 2000, 0x07D0) -// ============================================================================ - -/** - * @brief 停止开环运动请求数据(无业务字段)。 - * @note 命令码 2000(0x07D0)。下发零速度指令,终止当前持续运动。 - */ -message RobotControlStopRequestData -{ -} - -/** - * @brief 停止开环运动响应结果。 - */ -message RobotControlStopResult -{ - optional int32 ret_code = 1; ///< 返回码,0 表示成功 - optional string create_on = 2; ///< 时间戳 - optional string err_msg = 3; ///< 错误信息 -} - -/** - * @brief 停止开环运动命令。 - */ -message RobotControlStopCommand -{ - message Request - { - CommandHeader.Request header = 1; - RobotControlStopRequestData data = 2; - } - message Feedback - { - CommandHeader.Feedback header = 1; - RobotControlStopResult status = 2; - } -} - - -// ============================================================================ -// 18. 暂停/继续/取消导航任务(命令码 3001/3002/3003) -// ============================================================================ - -/** - * @brief 暂停当前导航任务(命令码 3001, 0x0BB9)。 - * @note 请求无业务数据。 - */ -message RobotTaskPauseCommand { - message Request { - CommandHeader.Request header = 1; - // 无 data 字段 - } - message Feedback { - CommandHeader.Feedback header = 1; - /** - * @brief 暂停结果。 - */ - message Result { - optional int32 ret_code = 1; ///< 返回码,0 表示成功 - optional string create_on = 2; ///< 时间戳 - optional string err_msg = 3; ///< 错误信息 - } - Result status = 2; - } -} - -/** - * @brief 继续当前导航任务(命令码 3002, 0x0BBA)。 - * @note 请求无业务数据。 - */ -message RobotTaskResumeCommand { - message Request { - CommandHeader.Request header = 1; - // 无 data 字段 - } - message Feedback { - CommandHeader.Feedback header = 1; - /** - * @brief 继续结果。 - */ - message Result { - optional int32 ret_code = 1; ///< 返回码,0 表示成功 - optional string create_on = 2; ///< 时间戳 - optional string err_msg = 3; ///< 错误信息 - } - Result status = 2; - } -} - -/** - * @brief 取消当前导航任务(命令码 3003, 0x0BBB)。 - * @note 请求无业务数据。 - */ -message RobotTaskCancelCommand { - message Request { - CommandHeader.Request header = 1; - // 无 data 字段 - } - message Feedback { - CommandHeader.Feedback header = 1; - /** - * @brief 取消结果。 - */ - message Result { - optional int32 ret_code = 1; ///< 返回码,0 表示成功 - optional string create_on = 2; ///< 时间戳 - optional string err_msg = 3; ///< 错误信息 - } - Result status = 2; - } -} \ No newline at end of file diff --git a/protos/cmvr/api/agv_service.proto b/protos/cmvr/api/agv_service.proto deleted file mode 100644 index 521ac025..00000000 --- a/protos/cmvr/api/agv_service.proto +++ /dev/null @@ -1,74 +0,0 @@ -/** - * @file agv_service.proto - * @brief 定义AGV服务的gRPC接口,聚合所有AGV相关命令。 - * 服务端实现类为 gRPCAGVServiceImpl。 - */ -syntax = "proto3"; - -import "cmvr/api/agv_command.proto"; - -package cmvr.api; - -service AgvService { - // 基本控制 - rpc GetStatusInfo(GetAgvStatusInfoCommand.Request) returns (GetAgvStatusInfoCommand.Feedback); - - rpc GetBatteryStatus(RobotStatusBatteryCommand.Request) returns (RobotStatusBatteryCommand.Feedback); - - rpc GetRobotLocation(RobotStatusLocCommand.Request) returns (RobotStatusLocCommand.Feedback); - - rpc RobotConfigDownloadMap(RobotConfigDownloadMapCommand.Request) returns (RobotConfigDownloadMapCommand.Feedback); - - - rpc GetMapStatus(RobotStatusMapCommand.Request) returns (RobotStatusMapCommand.Feedback); - - - rpc RobotConfigUploadMap(RobotConfigUploadMapCommand.Request) returns (RobotConfigUploadMapCommand.Feedback); - - // 抢占机器人控制权 - rpc RobotConfigLock(RobotConfigLockCommand.Request) returns (RobotConfigLockCommand.Feedback); - - // 查询当前控制权所有者 - rpc GetCurrentLockStatus(RobotStatusCurrentLockCommand.Request) returns (RobotStatusCurrentLockCommand.Feedback); - - // 开环速度运动控制 - rpc RobotMotionControl(RobotMotionControlCommand.Request) returns (RobotMotionControlCommand.Feedback); - - // 切换载入地图 命令码2022 - rpc RobotLoadMap(RobotLoadMapCommand.Request) returns (RobotLoadMapCommand.Feedback); - - - // 查询地图载入状态 命令码1022 - rpc QueryLoadMapStatus(RobotQueryLoadMapStatusCommand.Request) returns (RobotQueryLoadMapStatusCommand.Feedback); - - - // 查询当前地图站点列表 命令码1301 - rpc QueryStationList(QueryStationListCommand.Request) returns (QueryStationListCommand.Feedback); - - - // 指定路径导航 命令码3066 - rpc RobotGoTargetList(RobotGoTargetListCommand.Request) returns (RobotGoTargetListCommand.Feedback); - - // 查询当前导航状态 1020 robot_status_task_req - // 1020 查询当前实时导航 robot_status_task_req - rpc RobotStatusTaskCurrent(RobotStatusTaskCurrentCommand.Request) returns (RobotStatusTaskCurrentCommand.Feedback); - - // 批量查询任务状态 1110 robot_status_task_status_package_req - rpc RobotStatusTaskPackage(RobotStatusTaskPackageCommand.Request) returns (RobotStatusTaskPackageCommand.Feedback); - - - // 3051 单点站点自动规划导航 robot_task_gotarget_req 0x0BEB - rpc RobotGoTarget(RobotGoTargetCommand.Request) returns (RobotGoTargetCommand.Feedback); - - - - // 0x07D0 停止开环运动 - rpc RobotControlStop(RobotControlStopCommand.Request) returns (RobotControlStopCommand.Feedback); - - // 暂停当前导航任务 命令码 3001 (0x0BB9) - rpc RobotTaskPause(RobotTaskPauseCommand.Request) returns (RobotTaskPauseCommand.Feedback); - // 继续当前导航任务 命令码 3002 (0x0BBA) - rpc RobotTaskResume(RobotTaskResumeCommand.Request) returns (RobotTaskResumeCommand.Feedback); - // 取消当前导航任务 命令码 3003 (0x0BBB) - rpc RobotTaskCancel(RobotTaskCancelCommand.Request) returns (RobotTaskCancelCommand.Feedback); -} \ No newline at end of file diff --git a/protos/cmvr/config/agv_config/agv_config.proto b/protos/cmvr/config/agv_config/agv_config.proto deleted file mode 100644 index cb972f1d..00000000 --- a/protos/cmvr/config/agv_config/agv_config.proto +++ /dev/null @@ -1,20 +0,0 @@ -// camera_config.proto - 所有配置都在这里 -syntax = "proto3"; -package cmvr.config; - - -message AGVsrc1100Config{ - string id = 1; - string ip = 2; - bool enable = 3; - int32 port_status = 4; // 19204 状态查询 - int32 port_control = 5; // 19205 控制API - int32 port_nav = 6; // 19206 导航API - int32 port_config = 7; // 19207 配置API - int32 port_other = 8; // 19210 其他API - int32 port_push = 9; // 19301 推送API -} - -message AGVConfig{ - repeated AGVsrc1100Config src1100_agvs = 1; -} diff --git a/protos/cmvr/config/biohead_config/biohead_config.proto b/protos/cmvr/config/biohead_config/biohead_config.proto deleted file mode 100644 index 80c7d052..00000000 --- a/protos/cmvr/config/biohead_config/biohead_config.proto +++ /dev/null @@ -1,44 +0,0 @@ -// biohead.proto -syntax = "proto3"; - -package cmvr.config; - -// 眉毛配置 -message EyeBrowConfig { - string serial = 1; // 如 "64:0~3" - string offest = 2; // 偏移量,如 "90 90 90 90" - string jLmtUp = 3; // 上限,如 "90 170 155 110" - string jLmtLow = 4; // 下限,如 "20 77 90 20" -} - -// 眼睛配置 -message EyeConfig { - string serial = 1; // 如 "64:4~9" - string offest = 2; // 偏移量,如 "90 90 90 90 90 90" - string jLmtUp = 3; // 上限,如 "90 150 165 90 120 115" - string jLmtLow = 4; // 下限,如 "20 90 90 25 70 75" -} - -// 嘴巴配置 -message MouthConfig { - string serial = 1; // 如 "65:0~9" - string offest = 2; // 偏移量,如 "90 90 90 90 90 90 90 90 90 90" - string jLmtUp = 3; // 上限,如 "150 110 130 140 100 105 110 125 90 95" - string jLmtLow = 4; // 下限,如 "70 30 80 80 65 55 45 80 85 90" -} - -// BioHead配置 -message BioHeadConfig { - string id = 1; // 设备ID,如 "bio_head" - string serial = 2; // 串口路径,如 "/dev/ttyUSB0" - int32 ctrlFreq = 3; // 控制频率,如 50 - - EyeBrowConfig eyebrow = 4; // 眉毛配置 - EyeConfig eye = 5; // 眼睛配置 - MouthConfig mouth = 6; // 嘴巴配置 - bool enable = 7; // 是否启用 -} - -message HeadConfig { - repeated BioHeadConfig bioheads = 3; -} \ No newline at end of file diff --git a/protos/cmvr/config/monitor_config/monitor_config.proto b/protos/cmvr/config/monitor_config/monitor_config.proto deleted file mode 100644 index 368c4660..00000000 --- a/protos/cmvr/config/monitor_config/monitor_config.proto +++ /dev/null @@ -1,22 +0,0 @@ -syntax = "proto3"; - -package cmvr.config; - -// 文件夹监控配置 -message FolderConfig { - string fileDir = 1; // 文件夹路径 - int32 maxVolume = 2; // 最大容量(MB) -} - -// 磁盘监控配置 -message DiskMonitorConfig { - string id = 1; // 监控器ID,如 "file_monitor" - int32 freq = 2; // 监控频率(Hz) - repeated FolderConfig folders = 3; // 要监控的文件夹列表 - bool enable = 4; // 是否启用 -} - -// 监控管理器配置 -message MonitorManagerConfig { - DiskMonitorConfig disk_monitor = 1; // 磁盘监控配置 -} \ No newline at end of file diff --git a/protos/cmvr/config/robot_config/robot_config.proto b/protos/cmvr/config/robot_config/robot_config.proto deleted file mode 100644 index 1838f1eb..00000000 --- a/protos/cmvr/config/robot_config/robot_config.proto +++ /dev/null @@ -1,88 +0,0 @@ -syntax = "proto3"; - -package cmvr.config; - -// 电机配置 -message MotorConfig { - int32 id = 1; // 电机ID - string jointName = 2; // 关节名称 - float limitQLb = 3; // 位置下限 - float limitQUb = 4; // 位置上限 - float limitQd = 5; // 速度限制 -} - -// 通用的CAN设备配置(手臂、头部、腰部都可以复用) -message CanDeviceConfig { - string id = 1; // CAN ID - string devId = 2; // 设备ID - int32 channelId = 3; // 通道ID - bool enable = 4; // 是否启用 - string toolFrame = 5; // 工具坐标系(手臂需要,头/腰可选) - repeated MotorConfig motors = 6; // 电机列表 -} - -// CAN管理器配置 -message CanManagerConfig { - string id = 1; // CAN管理器ID - string devId = 2; // 设备ID - CanDeviceConfig left_arm = 3; // 左臂CAN - CanDeviceConfig right_arm = 4; // 右臂CAN - CanDeviceConfig head = 5; // 头部CAN - CanDeviceConfig waist = 6; // 腰部CAN -} - -// 人形机器人配置 -message HumanRobotConfig { - string id = 1; // 机器人ID,如 "hc01" - int32 dof = 2; // 自由度,如 14 - string urdf = 3; // URDF文件路径 - string baseLink = 4; // 基座link名称,如 "PELVIS_S" - string jointNames = 5; // 关节名称列表,逗号分隔 - string linkNames = 6; // link名称列表,逗号分隔 - int32 bufferSize = 7; // 缓冲区大小,如 50 - bool verbose = 8; // 是否详细输出 - int32 updFreq = 9; // 更新频率,如 500 - CanManagerConfig can_manager = 10; // CAN管理器配置 - bool enable = 11; // 是否启用 -} - -// EtherCAT主站配置 -message EthercatMasterConfig { - string interface = 1; // 网络接口,如 "eth0" - int32 cycle_time_us = 2; // 周期时间(微秒),默认1000 - bool enable = 3; // 是否启用 -} - -// EtherCAT电机驱动配置 -message EthercatMotorConfig { - int32 slave = 1; // 从站ID - string jointName = 2; // 关节名称 - float limitQLb = 3; // 位置下限 - float limitQUb = 4; // 位置上限 - float limitQd = 5; // 速度限制 -} - -// EtherCAT机器人配置 -message EthercatRobotConfig { - string id = 1; // 机器人ID - int32 dof = 2; // 自由度 - string urdf = 3; // URDF文件路径 - string baseLink = 4; // 基座link名称 - string jointNames = 5; // 关节名称列表,逗号分隔 - string linkNames = 6; // link名称列表,逗号分隔 - int32 bufferSize = 7; // 缓冲区大小 - bool verbose = 8; // 是否详细输出 - int32 updFreq = 9; // 更新频率(Hz) - bool enable = 10; // 是否启用 - - EthercatMasterConfig master = 11; // EtherCAT主站配置 - repeated EthercatMotorConfig motors = 12; // 电机配置列表 -} - - - -// 主配置 -message RobotConfig { - repeated HumanRobotConfig humanrobots = 1; - repeated EthercatRobotConfig ethercat_robots = 2; // 新增的EtherCAT机器人 -} \ No newline at end of file diff --git a/protos/cmvr/config/server_config/server_config.proto b/protos/cmvr/config/server_config/server_config.proto deleted file mode 100644 index 911b12ca..00000000 --- a/protos/cmvr/config/server_config/server_config.proto +++ /dev/null @@ -1,10 +0,0 @@ -syntax = "proto3"; -package cmvr.config; - -message GrpcServerConfig{ - string port = 1; -} - -message ServerConfig{ - GrpcServerConfig grpc_server = 1; -} \ No newline at end of file diff --git a/request.txt b/request.txt index 9e9809c7..9a4d60e2 100644 --- a/request.txt +++ b/request.txt @@ -33,6 +33,5 @@ third_party/modbus/3.1.11 third_party/visp/3.7.0 third_party/mainif/0.0.5 third_party/matplotplusplus/1.2.0 -third_party/soem From 9b6f1b8d21be012cfa81e2ad17052db6c8fe5240 Mon Sep 17 00:00:00 2001 From: xtkuang <87661715@qq.com> Date: Wed, 1 Jul 2026 09:02:11 +0800 Subject: [PATCH 9/9] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8D=8E=E6=B2=BF?= =?UTF-8?q?=E6=9C=BA=E5=99=A8=E4=BA=BA=E6=9C=BA=E6=A2=B0=E8=87=82=E7=BB=84?= =?UTF-8?q?=E5=BB=BA=E5=B9=B6=E5=AE=8C=E6=88=90=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmvr-es/config/devices/arm/huayan_arm.pb.txt | 21 + cmvr-es/config/manager/device_manager.pb.txt | 15 +- cmvr-es/config/manager/task_manager.pb.txt | 2 +- cmvr-es/devices/arm/CMakeLists.txt | 2 + cmvr-es/devices/arm/huayan_arm/CMakeLists.txt | 28 + cmvr-es/devices/arm/huayan_arm/huayan_arm.cpp | 885 ++++ cmvr-es/devices/arm/huayan_arm/huayan_arm.h | 143 + cmvr-es/devices/arm/robot_arm_factory.h | 4 + .../huayan_arm/v1.0/include/HR_Pro.h | 4528 +++++++++++++++++ .../huayan_arm/v1.0/lib/libHR_Pro.so | Bin 0 -> 1856856 bytes .../cmvr/config/arm_config/arm_config.proto | 1 + request.txt | 1 + 12 files changed, 5625 insertions(+), 5 deletions(-) create mode 100644 cmvr-es/config/devices/arm/huayan_arm.pb.txt create mode 100644 cmvr-es/devices/arm/huayan_arm/CMakeLists.txt create mode 100644 cmvr-es/devices/arm/huayan_arm/huayan_arm.cpp create mode 100644 cmvr-es/devices/arm/huayan_arm/huayan_arm.h create mode 100644 dependency/x86/third_party/huayan_arm/v1.0/include/HR_Pro.h create mode 100644 dependency/x86/third_party/huayan_arm/v1.0/lib/libHR_Pro.so diff --git a/cmvr-es/config/devices/arm/huayan_arm.pb.txt b/cmvr-es/config/devices/arm/huayan_arm.pb.txt new file mode 100644 index 00000000..945405d8 --- /dev/null +++ b/cmvr-es/config/devices/arm/huayan_arm.pb.txt @@ -0,0 +1,21 @@ +arm { + robot_arms { + id: "huayan_arm" + + vendor { + brand: VENDOR_ROBOT_ARM_BRAND_HUAYAN_ARM + ip: "192.168.0.10" + port: 10003 + model: "HuayanRobot" + dof: 6 + joint_names: "joint_1" + joint_names: "joint_2" + joint_names: "joint_3" + joint_names: "joint_4" + joint_names: "joint_5" + joint_names: "joint_6" + base_frame: "Base" + tool_frame: "TCP" + } + } +} diff --git a/cmvr-es/config/manager/device_manager.pb.txt b/cmvr-es/config/manager/device_manager.pb.txt index 3f30fc04..6b05c688 100644 --- a/cmvr-es/config/manager/device_manager.pb.txt +++ b/cmvr-es/config/manager/device_manager.pb.txt @@ -7,14 +7,14 @@ device_manager { id: "right_hand_cam" type: DEVICE_TYPE_CAMERA config_file: "devices/camera/camera.pb.txt" - enable: true + enable: false } devices { id: "hand2" type: DEVICE_TYPE_DEXHAND config_file: "devices/dexhand/dexhand.pb.txt" - enable: true + enable: false } devices { @@ -28,14 +28,14 @@ device_manager { id: "ti5_motors" type: DEVICE_TYPE_MOTOR_SYSTEM config_file: "devices/motor/ti5_motors.pb.txt" - enable: true + enable: false } devices { id: "right_arm" type: DEVICE_TYPE_ROBOT_ARM config_file: "devices/arm/arm.pb.txt" - enable: true + enable: false } devices { @@ -45,6 +45,13 @@ device_manager { enable: false } + devices { + id: "huayan_arm" + type: DEVICE_TYPE_ROBOT_ARM + config_file: "devices/arm/huayan_arm.pb.txt" + enable: true + } + devices { id: "bio_head" type: DEVICE_TYPE_BIO_HEAD_ROBOT diff --git a/cmvr-es/config/manager/task_manager.pb.txt b/cmvr-es/config/manager/task_manager.pb.txt index b8cfcc76..db13dfa9 100644 --- a/cmvr-es/config/manager/task_manager.pb.txt +++ b/cmvr-es/config/manager/task_manager.pb.txt @@ -5,7 +5,7 @@ task_manager { run_mode: TASK_RUN_MODE_PERIODIC_STEP control_period_s: 0.001 config_file: "tasks/touch_screen_task/touch_screen_task.pb.txt" - enable: true + enable: false } tasks { id: "grpc_server" diff --git a/cmvr-es/devices/arm/CMakeLists.txt b/cmvr-es/devices/arm/CMakeLists.txt index f6d9fd39..d2780c71 100644 --- a/cmvr-es/devices/arm/CMakeLists.txt +++ b/cmvr-es/devices/arm/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(motor_robot_arm) add_subdirectory(aubo_arm) +add_subdirectory(huayan_arm) add_library(robot_arm INTERFACE) @@ -9,6 +10,7 @@ target_link_libraries(robot_arm INTERFACE cmvr_es::device::motor_robot_arm cmvr_es::device::aubo_arm + cmvr_es::device::huayan_arm cmvr_es::proto ) diff --git a/cmvr-es/devices/arm/huayan_arm/CMakeLists.txt b/cmvr-es/devices/arm/huayan_arm/CMakeLists.txt new file mode 100644 index 00000000..617fab7d --- /dev/null +++ b/cmvr-es/devices/arm/huayan_arm/CMakeLists.txt @@ -0,0 +1,28 @@ +add_library(huayan_arm SHARED huayan_arm.cpp) + +set(HUAYAN_ARM_SDK_DIR ${CMAKE_SOURCE_DIR}/dependency/${ARCH}/third_party/huayan_arm/v1.0) + +target_include_directories(huayan_arm + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + PRIVATE + ${HUAYAN_ARM_SDK_DIR}/include +) + +target_link_directories(huayan_arm + PRIVATE + ${HUAYAN_ARM_SDK_DIR}/lib +) + +target_link_libraries(huayan_arm + PUBLIC + cmvr_es::proto + PRIVATE + HR_Pro + glog +) + +add_library(cmvr_es::device::huayan_arm ALIAS huayan_arm) + +install(TARGETS huayan_arm LIBRARY DESTINATION lib) +install(FILES ${HUAYAN_ARM_SDK_DIR}/lib/libHR_Pro.so DESTINATION lib) \ No newline at end of file diff --git a/cmvr-es/devices/arm/huayan_arm/huayan_arm.cpp b/cmvr-es/devices/arm/huayan_arm/huayan_arm.cpp new file mode 100644 index 00000000..3ac77065 --- /dev/null +++ b/cmvr-es/devices/arm/huayan_arm/huayan_arm.cpp @@ -0,0 +1,885 @@ +#include "huayan_arm.h" + +#include +#include +#include +#include + +#include "huayan_arm/v1.0/include/HR_Pro.h" +#include "common/base/logging/logger.h" + +namespace cmvr::device { +namespace { + +constexpr double kPi = 3.14159265358979323846; +constexpr double kDefaultMoveJVelocityDeg = 30.0; +constexpr double kDefaultMoveJAccelerationDeg = 60.0; +constexpr double kDefaultMoveLVelocityMm = 100.0; +constexpr double kDefaultMoveLAccelerationMm = 200.0; + +double radToDeg(const double value) +{ + return value * 180.0 / kPi; +} + +double degToRad(const double value) +{ + return value * kPi / 180.0; +} + +double metersToMm(const double value) +{ + return value * 1000.0; +} + +double mmToMeters(const double value) +{ + return value / 1000.0; +} + +std::vector defaultJointNames(const std::size_t dof) +{ + std::vector names; + names.reserve(dof); + for (std::size_t i = 0; i < dof; ++i) { + names.push_back("joint_" + std::to_string(i + 1)); + } + return names; +} + +std::array toSix(const std::vector& values, const double fill = 0.0) +{ + std::array out{fill, fill, fill, fill, fill, fill}; + const auto n = std::min(out.size(), values.size()); + for (std::size_t i = 0; i < n; ++i) { + out[i] = values[i]; + } + return out; +} + +std::vector poseToHrCoord(const CartesianPose& pose) +{ + return {metersToMm(pose.x), + metersToMm(pose.y), + metersToMm(pose.z), + radToDeg(pose.rx), + radToDeg(pose.ry), + radToDeg(pose.rz)}; +} + +std::vector zeroHrFrame() +{ + return {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; +} + +} // namespace + +HuayanRobot::HuayanRobot(const config::RobotArmConfig& cfg) + : cfg_(cfg) +{ + id_ = cfg.id(); + if (cfg.has_vendor()) { + vendor_cfg_ = cfg.vendor(); + } + + ip_ = vendor_cfg_.ip(); + port_ = vendor_cfg_.port() > 0 ? vendor_cfg_.port() : 10003; + tcp_name_ = vendor_cfg_.tool_frame().empty() ? "TCP" : vendor_cfg_.tool_frame(); + ucs_name_ = vendor_cfg_.base_frame().empty() ? "Base" : vendor_cfg_.base_frame(); + + const auto dof = vendor_cfg_.dof() > 0 ? static_cast(vendor_cfg_.dof()) : 6U; + model_.name = vendor_cfg_.model().empty() ? "HuayanRobot" : vendor_cfg_.model(); + model_.manufacturer = "Huayan"; + model_.dof = dof; + model_.joint_names.assign(vendor_cfg_.joint_names().begin(), vendor_cfg_.joint_names().end()); + if (model_.joint_names.empty()) { + model_.joint_names = defaultJointNames(dof); + } + if (model_.joint_names.size() != dof) { + CMVR_LOG(ERROR) << "[HuayanRobot] joint_names size mismatch, id=" << id_; + model_.joint_names = defaultJointNames(dof); + } +} + +HuayanRobot::~HuayanRobot() +{ + (void)disconnect(); +} + +bool HuayanRobot::init() +{ + if (ip_.empty()) { + CMVR_LOG(ERROR) << "[HuayanRobot] ip is empty, id=" << id_; + return false; + } + const auto result = connect(ip_, port_); + if (!result.ok()) { + CMVR_LOG(ERROR) << "[HuayanRobot] init failed: " << result.message; + return false; + } + return true; +} + +bool HuayanRobot::stop() +{ + return stopMotion().ok(); +} + +ArmState HuayanRobot::getRobotState() const +{ + const auto hr_state = readHrState_(); + + ArmState state; + state.connected = isConnected(); + state.powered_on = hr_state.valid ? hr_state.electrified != 0 : state.connected; + state.brake_released = hr_state.valid ? hr_state.brake != 0 : state.connected; + state.moving = hr_state.valid ? hr_state.moving != 0 : busy_.load(); + state.program_running = state.moving; + state.protective_stopped = hr_state.valid ? hr_state.safeguard != 0 : false; + state.emergency_stopped = hr_state.valid ? hr_state.emergency_stop != 0 : false; + state.fault = hr_state.valid ? hr_state.error != 0 : false; + state.robot_mode = getRobotMode(); + state.safety_mode = getSafetyMode(); + state.control_mode = getControlMode(); + state.speed_scaling = speed_scaling_; + state.actual_joint_state = getJointState(); + state.target_joint_state = state.actual_joint_state; + state.actual_tcp_pose = readTcpPose_(); + state.actual_tcp_velocity = readTcpVelocity_(); + return state; +} + +JointGroupState HuayanRobot::getJointState() const +{ + JointGroupState state; + state.position = readJointPositionRad_(); + state.velocity = readJointVelocityRad_(); + state.effort.assign(model_.dof, 0.0); + return state; +} + +CartesianPose HuayanRobot::getTcpPose(const FrameType frame) const +{ + (void)frame; + return readTcpPose_(); +} + +RobotMode HuayanRobot::getRobotMode() const +{ + if (!isConnected()) { + return RobotMode::Disconnected; + } + + const auto state = readHrState_(); + if (!state.valid) { + return busy_.load() ? RobotMode::Running : RobotMode::Idle; + } + if (state.error != 0) { + return RobotMode::Fault; + } + if (state.emergency_stop != 0) { + return RobotMode::Stopped; + } + if (state.paused != 0) { + return RobotMode::Paused; + } + if (state.electrified == 0) { + return RobotMode::PowerOff; + } + return state.moving != 0 ? RobotMode::Running : RobotMode::Idle; +} + +SafetyMode HuayanRobot::getSafetyMode() const +{ + const auto state = readHrState_(); + if (!state.valid) { + return SafetyMode::Unknown; + } + if (state.error != 0) { + return SafetyMode::Fault; + } + if (state.emergency_stop != 0) { + return SafetyMode::EmergencyStop; + } + if (state.safeguard != 0) { + return SafetyMode::SafeguardStop; + } + return SafetyMode::Normal; +} + +Result HuayanRobot::torqueOn() +{ + const auto ready = ensureConnected_("torqueOn"); + if (!ready.ok()) { + return ready; + } + std::lock_guard lock(mutex_); + return hrResult_(HRIF_GrpEnable(box_id_, robot_id_), "GrpEnable"); +} + +Result HuayanRobot::torqueOff() +{ + + const auto ready = ensureConnected_("torqueOff"); + if (!ready.ok()) { + return ready; + } + + std::lock_guard lock(mutex_); + return hrResult_(HRIF_GrpDisable(box_id_, robot_id_), "GrpDisable"); +} + +Result HuayanRobot::calibrateZeroQ(const std::string& joint_name) +{ + (void)joint_name; + return unsupported_("calibrateZeroQ"); +} + +Result HuayanRobot::emergencyStop() +{ + return stopMotion(); +} + +Result HuayanRobot::setSpeedScaling(const double scaling) +{ + if (scaling < 0.0 || scaling > 1.0) { + return Result::failure(ArmErrorCode::InvalidArgument, "speed scaling must be in [0, 1]"); + } + speed_scaling_ = scaling; + if (isConnected()) { + return hrResult_(HRIF_SetOverride(box_id_, robot_id_, scaling), "SetOverride"); + } + return Result::success(); +} + +bool HuayanRobot::isProtectiveStopped() const +{ + const auto state = readHrState_(); + return state.valid && state.safeguard != 0; +} + +bool HuayanRobot::isEmergencyStopped() const +{ + const auto state = readHrState_(); + return state.valid && state.emergency_stop != 0; +} + +bool HuayanRobot::isFault() const +{ + const auto state = readHrState_(); + return state.valid && state.error != 0; +} + +Result HuayanRobot::moveJ(const JointPositionCommand& target, const MotionOptions& options) +{ + std::string error; + if (!validDof_(target.position.size(), error)) { + return Result::failure(ArmErrorCode::InvalidDof, error); + } + const auto ready = ensureConnected_("moveJ"); + if (!ready.ok()) { + return ready; + } + if (busy_.exchange(true)) { + return Result::failure(ArmErrorCode::RobotNotReady, "[HuayanRobot] arm is busy: " + id_); + } + + std::array q_deg{}; + for (std::size_t i = 0; i < std::min(target.position.size(), q_deg.size()); ++i) { + q_deg[i] = radToDeg(target.position[i]); + } + + const double velocity = options.velocity > 0.0 ? radToDeg(options.velocity) : kDefaultMoveJVelocityDeg; + const double acceleration = options.acceleration > 0.0 ? radToDeg(options.acceleration) : kDefaultMoveJAccelerationDeg; + const double blend = metersToMm(options.blend_radius); + const std::string command_id = nextCommandId_(); + + const int ret = HRIF_MoveJ(box_id_, robot_id_, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + q_deg[0], q_deg[1], q_deg[2], q_deg[3], q_deg[4], q_deg[5], + tcp_name_, ucs_name_, velocity * speed_scaling_, acceleration, blend, + 1, 0, 0, 0, command_id); + if (ret != 0) { + busy_.store(false); + return hrResult_(ret, "moveJ"); + } + const auto wait_result = waitMotionDone_("moveJ", 60000); + busy_.store(false); + return wait_result; +} + +Result HuayanRobot::speedJ(const JointVelocityCommand& velocity, const double acceleration, const double duration) +{ + std::string error; + if (!validDof_(velocity.velocity.size(), error)) { + return Result::failure(ArmErrorCode::InvalidDof, error); + } + const auto ready = ensureConnected_("speedJ"); + if (!ready.ok()) { + return ready; + } + + std::array qd_deg{}; + for (std::size_t i = 0; i < std::min(velocity.velocity.size(), qd_deg.size()); ++i) { + qd_deg[i] = radToDeg(velocity.velocity[i]); + } + const double acc_deg = acceleration > 0.0 ? radToDeg(acceleration) : kDefaultMoveJAccelerationDeg; + const double runtime = duration > 0.0 ? duration : 0.1; + + const int ret = HRIF_SpeedJ(box_id_, robot_id_, + qd_deg[0], qd_deg[1], qd_deg[2], qd_deg[3], qd_deg[4], qd_deg[5], + acc_deg, runtime); + if (ret != 0) { + busy_.store(false); + return hrResult_(ret, "SpeedJ"); + } + const auto wait_result = waitMotionDone_("SpeedJ", 60000); + busy_.store(false); + return wait_result; +} + +Result HuayanRobot::stopJ(const double acceleration) +{ + (void)acceleration; + return stopMotion(); +} + +Result HuayanRobot::moveL(const CartesianPose& target, const MotionOptions& options, const FrameType frame) +{ + (void)frame; + const auto ready = ensureConnected_("moveL"); + if (!ready.ok()) { + return ready; + } + if (busy_.exchange(true)) { + return Result::failure(ArmErrorCode::RobotNotReady, "[HuayanRobot] arm is busy: " + id_); + } + + const auto pose = poseToHrCoord(target); + const auto q_deg = toSix(currentJointPositionDeg_()); + const double velocity = options.velocity > 0.0 ? metersToMm(options.velocity) : kDefaultMoveLVelocityMm; + const double acceleration = options.acceleration > 0.0 ? metersToMm(options.acceleration) : kDefaultMoveLAccelerationMm; + const double blend = metersToMm(options.blend_radius); + const std::string command_id = nextCommandId_(); + + const int ret = HRIF_MoveL(box_id_, robot_id_, + pose[0], pose[1], pose[2], pose[3], pose[4], pose[5], + q_deg[0], q_deg[1], q_deg[2], q_deg[3], q_deg[4], q_deg[5], + tcp_name_, ucs_name_, velocity * speed_scaling_, acceleration, blend, + 0, 0, 0, command_id); + if (ret != 0) { + busy_.store(false); + return hrResult_(ret, "moveL"); + } + const auto wait_result = waitMotionDone_("moveL", 60000); + busy_.store(false); + return wait_result; +} + +Result HuayanRobot::speedL(const CartesianVelocity& velocity, + const double acceleration, + const double duration, + const FrameType frame) +{ + const auto ready = ensureConnected_("speedL"); + if (!ready.ok()) { + return ready; + } + const double vx_mm = metersToMm(velocity.vx); + const double vy_mm = metersToMm(velocity.vy); + const double vz_mm = metersToMm(velocity.vz); + const double wx_deg = radToDeg(velocity.wx); + const double wy_deg = radToDeg(velocity.wy); + const double wz_deg = radToDeg(velocity.wz); + + const double linear_acc_mm = + acceleration > 0.0 ? metersToMm(acceleration) : kDefaultMoveLAccelerationMm; + + const double angular_acc_deg = + acceleration > 0.0 ? radToDeg(acceleration) : kDefaultMoveJAccelerationDeg; + + const double runtime = duration > 0.0 ? duration : 0.5; + + std::lock_guard lock(mutex_); + servo_mode_.store(false); + const int ret = HRIF_SpeedL(box_id_, robot_id_, vx_mm, vy_mm, vz_mm, + wx_deg, wy_deg, wz_deg, linear_acc_mm, angular_acc_deg, runtime); + if (ret != 0) { + busy_.store(false); + return hrResult_(ret, "SpeedL"); + } + const auto wait_result = waitMotionDone_("SpeedL", 60000); + busy_.store(false); + return wait_result; +} + +Result HuayanRobot::stopL(const double acceleration) +{ + (void)acceleration; + return stopMotion(); +} + +Result HuayanRobot::stopMotion() +{ + if (!isConnected()) { + busy_.store(false); + servo_mode_.store(false); + return Result::success(); + } + + const auto result = hrResult_(HRIF_GrpStop(box_id_, robot_id_), "GrpStop"); + busy_.store(false); + servo_mode_.store(false); + return result; +} + +Result HuayanRobot::startServoMode(const ServoOptions& options) +{ + const auto ready = ensureConnected_("startServoMode"); + if (!ready.ok()) { + return ready; + } + const double period = options.period > 0.0 ? options.period : 0.008; + const double lookahead = options.lookahead_time > 0.0 ? options.lookahead_time : 0.1; + const auto result = hrResult_(HRIF_StartServo(box_id_, robot_id_, period, lookahead), "StartServo"); + if (result.ok()) { + servo_mode_.store(true); + } + return result; +} + +Result HuayanRobot::servoJ(const JointPositionCommand& target) +{ + std::string error; + if (!validDof_(target.position.size(), error)) { + return Result::failure(ArmErrorCode::InvalidDof, error); + } + const auto ready = ensureConnected_("servoJ"); + if (!ready.ok()) { + return ready; + } + + std::array q_deg{}; + for (std::size_t i = 0; i < std::min(target.position.size(), q_deg.size()); ++i) { + q_deg[i] = radToDeg(target.position[i]); + } + return hrResult_(HRIF_PushServoJ(box_id_, robot_id_, + q_deg[0], q_deg[1], q_deg[2], q_deg[3], q_deg[4], q_deg[5]), + "servoJ"); +} + +Result HuayanRobot::servoL(const CartesianPose& target, const FrameType frame) +{ + (void)frame; + const auto ready = ensureConnected_("servoL"); + if (!ready.ok()) { + return ready; + } + + auto coord = poseToHrCoord(target); + auto ucs = zeroHrFrame(); + auto tcp = zeroHrFrame(); + return hrResult_(HRIF_PushServoP(box_id_, robot_id_, coord, ucs, tcp), "servoL"); +} + +Result HuayanRobot::servoSpeedJ(const JointVelocityCommand& velocity) +{ + (void)velocity; + return unsupported_("servoSpeedJ"); +} + +Result HuayanRobot::servoSpeedL(const CartesianVelocity& velocity, const FrameType frame) +{ + (void)velocity; + (void)frame; + return unsupported_("servoSpeedL"); +} + +Result HuayanRobot::stopServoMode() +{ + servo_mode_.store(false); + return stopMotion(); +} + +Result HuayanRobot::connect(const std::string& ip, const int port) +{ + if (connected_.load()) { + return Result::success(); + } + if (ip.empty()) { + return Result::failure(ArmErrorCode::InvalidArgument, "[HuayanRobot] ip is empty"); + } + + std::lock_guard lock(mutex_); + const int use_port = port > 0 ? port : 10003; + const auto result = hrResult_(HRIF_Connect(box_id_, ip.c_str(), static_cast(use_port)), + "Connect"); + if (!result.ok()) { + connected_.store(false); + return result; + } + ip_ = ip; + port_ = use_port; + connected_.store(true); + return Result::success(); +} + +Result HuayanRobot::disconnect() +{ + if (connected_.load() || HRIF_IsConnected(box_id_)) { + const auto result = hrResult_(HRIF_DisConnect(box_id_), "DisConnect"); + connected_.store(false); + busy_.store(false); + servo_mode_.store(false); + return result; + } + + connected_.store(false); + busy_.store(false); + servo_mode_.store(false); + return Result::success(); +} + +bool HuayanRobot::isConnected() const +{ + return connected_.load() && HRIF_IsConnected(box_id_); +} + +Result HuayanRobot::shutdown() +{ + if (!isConnected()) { + return Result::success(); + } + auto result = hrResult_(HRIF_ShutdownRobot(box_id_), "ShutdownRobot"); + if (!result.ok()) { + return result; + } + return disconnect(); +} + +Result HuayanRobot::clearFault() +{ + const auto ready = ensureConnected_("clearFault"); + if (!ready.ok()) { + return ready; + } + return hrResult_(HRIF_GrpReset(box_id_, robot_id_), "GrpReset"); +} + +Result HuayanRobot::loadProgram(const std::string& program_name) +{ + (void)program_name; + return Result::success(); +} + +Result HuayanRobot::playProgram() +{ + const auto ready = ensureConnected_("playProgram"); + if (!ready.ok()) { + return ready; + } + return hrResult_(HRIF_StartScript(box_id_), "StartScript"); +} + +Result HuayanRobot::pauseProgram() +{ + const auto ready = ensureConnected_("pauseProgram"); + if (!ready.ok()) { + return ready; + } + return hrResult_(HRIF_PauseScript(box_id_), "PauseScript"); +} + +Result HuayanRobot::stopProgram() +{ + const auto ready = ensureConnected_("stopProgram"); + if (!ready.ok()) { + return ready; + } + return hrResult_(HRIF_StopScript(box_id_), "StopScript"); +} + +std::vector HuayanRobot::ik(const std::string& base_link, + const std::string& ee_link, + const CartesianPose& pose) +{ + (void)base_link; + (void)ee_link; + (void)pose; + CMVR_LOG(ERROR) << "[HuayanRobot] ik is not implemented"; + return {}; +} + +CartesianPose HuayanRobot::fk(const std::string& base_link, const std::string& ee_link) +{ + (void)base_link; + (void)ee_link; + return readTcpPose_(); +} + +CartesianPose HuayanRobot::fk(const bool is_tcp) +{ + (void)is_tcp; + return readTcpPose_(); +} + +CartesianVelocity HuayanRobot::getSpeedLCommandTwistBase() const +{ + return readTcpVelocity_(); +} + +Result HuayanRobot::ensureConnected_(const std::string& context) const +{ + if (!isConnected()) { + return Result::failure(ArmErrorCode::NotConnected, + "[HuayanRobot] " + context + " failed: arm is not connected"); + } + return Result::success(); +} + +Result HuayanRobot::unsupported_(const std::string& name) const +{ + const std::string message = "[HuayanRobot] " + name + " is not implemented"; + CMVR_LOG(ERROR) << message; + return Result::failure(ArmErrorCode::UnsupportedCommand, message); +} + +Result HuayanRobot::hrResult_(const int code, const std::string& context) const +{ + if (code == 0) { + return Result::success(); + } + + std::string sdk_message; + (void)HRIF_GetErrorCodeStr(box_id_, code, sdk_message); + std::ostringstream oss; + oss << "[HuayanRobot] " << context << " failed, code=" << code; + if (!sdk_message.empty()) { + oss << ", message=" << sdk_message; + } + const auto message = oss.str(); + CMVR_LOG(ERROR) << message; + return Result::failure(ArmErrorCode::CommandFailed, message); +} + +bool HuayanRobot::validDof_(const std::size_t size, std::string& error) const +{ + if (size != model_.dof) { + error = "[HuayanRobot] command dof mismatch, expected=" + std::to_string(model_.dof) + + ", actual=" + std::to_string(size); + CMVR_LOG(ERROR) << error; + return false; + } + if (model_.dof > 6) { + error = "[HuayanRobot] command dof exceeds SDK limit: " + std::to_string(model_.dof); + CMVR_LOG(ERROR) << error; + return false; + } + return true; +} + +HuayanRobot::HrState HuayanRobot::readHrState_() const +{ + HrState state; + if (!isConnected()) { + return state; + } + + const int ret = HRIF_ReadRobotState(box_id_, robot_id_, + state.moving, + state.enabled, + state.error, + state.error_code, + state.error_axis, + state.brake, + state.paused, + state.emergency_stop, + state.safeguard, + state.electrified, + state.connected_to_box, + state.blending_done, + state.in_pos); + state.valid = ret == 0; + if (ret != 0) { + CMVR_LOG(ERROR) << "[HuayanRobot] read robot state failed, code=" << ret; + } + return state; +} + +std::vector HuayanRobot::readJointPositionRad_() const +{ + std::vector q(model_.dof, 0.0); + if (!isConnected()) { + return q; + } + + double j1 = 0.0; + double j2 = 0.0; + double j3 = 0.0; + double j4 = 0.0; + double j5 = 0.0; + double j6 = 0.0; + const int ret = HRIF_ReadActJointPos(box_id_, robot_id_, j1, j2, j3, j4, j5, j6); + if (ret != 0) { + CMVR_LOG(ERROR) << "[HuayanRobot] read joint position failed, code=" << ret; + return q; + } + + const std::array values{j1, j2, j3, j4, j5, j6}; + for (std::size_t i = 0; i < std::min(q.size(), values.size()); ++i) { + q[i] = degToRad(values[i]); + } + return q; +} + +std::vector HuayanRobot::readJointVelocityRad_() const +{ + std::vector qd(model_.dof, 0.0); + if (!isConnected()) { + return qd; + } + + double j1 = 0.0; + double j2 = 0.0; + double j3 = 0.0; + double j4 = 0.0; + double j5 = 0.0; + double j6 = 0.0; + const int ret = HRIF_ReadActJointVel(box_id_, robot_id_, j1, j2, j3, j4, j5, j6); + if (ret != 0) { + CMVR_LOG(ERROR) << "[HuayanRobot] read joint velocity failed, code=" << ret; + return qd; + } + + const std::array values{j1, j2, j3, j4, j5, j6}; + for (std::size_t i = 0; i < std::min(qd.size(), values.size()); ++i) { + qd[i] = degToRad(values[i]); + } + return qd; +} + +CartesianPose HuayanRobot::readTcpPose_() const +{ + CartesianPose pose; + if (!isConnected()) { + return pose; + } + + double x = 0.0; + double y = 0.0; + double z = 0.0; + double rx = 0.0; + double ry = 0.0; + double rz = 0.0; + const int ret = HRIF_ReadActTcpPos(box_id_, robot_id_, x, y, z, rx, ry, rz); + if (ret != 0) { + CMVR_LOG(ERROR) << "[HuayanRobot] read tcp pose failed, code=" << ret; + return pose; + } + + pose.x = mmToMeters(x); + pose.y = mmToMeters(y); + pose.z = mmToMeters(z); + pose.rx = degToRad(rx); + pose.ry = degToRad(ry); + pose.rz = degToRad(rz); + return pose; +} + +CartesianVelocity HuayanRobot::readTcpVelocity_() const +{ + CartesianVelocity velocity; + if (!isConnected()) { + return velocity; + } + + double x = 0.0; + double y = 0.0; + double z = 0.0; + double rx = 0.0; + double ry = 0.0; + double rz = 0.0; + const int ret = HRIF_ReadActTcpVel(box_id_, robot_id_, x, y, z, rx, ry, rz); + if (ret != 0) { + CMVR_LOG(ERROR) << "[HuayanRobot] read tcp velocity failed, code=" << ret; + return velocity; + } + + velocity.vx = mmToMeters(x); + velocity.vy = mmToMeters(y); + velocity.vz = mmToMeters(z); + velocity.wx = degToRad(rx); + velocity.wy = degToRad(ry); + velocity.wz = degToRad(rz); + return velocity; +} + +std::vector HuayanRobot::currentJointPositionDeg_() const +{ + const auto q_rad = readJointPositionRad_(); + std::vector q_deg(q_rad.size(), 0.0); + for (std::size_t i = 0; i < q_rad.size(); ++i) { + q_deg[i] = radToDeg(q_rad[i]); + } + return q_deg; +} + +std::string HuayanRobot::nextCommandId_() const +{ + return id_ + "_" + std::to_string(++command_seq_); +} + +Result HuayanRobot::waitMotionDone_(const std::string& context, const int timeout_ms) const +{ + const auto start = std::chrono::steady_clock::now(); + while (true) { + bool done = false; + const int ret = HRIF_IsMotionDone(box_id_, robot_id_, done); + if (ret != 0) { + return hrResult_(ret, "IsMotionDone(" + context + ")"); + } + const auto state = readHrState_(); + if (state.valid) { + if (state.error != 0) { + return Result::failure( + ArmErrorCode::CommandFailed, + "[HuayanRobot] " + context + " failed: robot error, code=" + + std::to_string(state.error_code)); + } + + if (state.emergency_stop != 0) { + return Result::failure( + ArmErrorCode::CommandFailed, + "[HuayanRobot] " + context + " failed: emergency stop"); + } + + if (state.safeguard != 0) { + return Result::failure( + ArmErrorCode::CommandFailed, + "[HuayanRobot] " + context + " failed: safeguard stop"); + } + } + + if (done) { + return Result::success(); + } + + const auto elapsed = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start).count(); + + if (elapsed > timeout_ms) { + { + std::lock_guard lock(mutex_); + (void)HRIF_GrpStop(box_id_, robot_id_); + } + + return Result::failure( + ArmErrorCode::CommandFailed, + "[HuayanRobot] " + context + " timeout"); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } +} + +} // namespace cmvr::device diff --git a/cmvr-es/devices/arm/huayan_arm/huayan_arm.h b/cmvr-es/devices/arm/huayan_arm/huayan_arm.h new file mode 100644 index 00000000..32909710 --- /dev/null +++ b/cmvr-es/devices/arm/huayan_arm/huayan_arm.h @@ -0,0 +1,143 @@ +// +// Created by cmvr on 2026/6/29. +// + +#ifndef CMVR_ES_HUAYAN_ARM_H +#define CMVR_ES_HUAYAN_ARM_H + +#ifndef CMVR_ES_HUAYAN_ROBOT_H +#define CMVR_ES_HUAYAN_ROBOT_H + +#include +#include +#include +#include +#include + +#include "cmvr/config/arm_config/arm_config.pb.h" +#include "devices/arm/robot_arm.h" + +namespace cmvr::device { + +class HuayanRobot final : public RobotArm { +public: + explicit HuayanRobot(const config::RobotArmConfig& cfg); + ~HuayanRobot() override; + + std::string typeName() const override { return "HuayanRobot"; } + bool init() override; + bool stop() override; + + RobotModel getRobotModel() const override { return model_; } + std::size_t getDof() const override { return model_.dof; } + ArmState getRobotState() const override; + JointGroupState getJointState() const override; + CartesianPose getTcpPose(FrameType frame = FrameType::Base) const override; + RobotMode getRobotMode() const override; + SafetyMode getSafetyMode() const override; + ControlMode getControlMode() const override { return servo_mode_.load() ? ControlMode::Servo : ControlMode::Position; } + + Result torqueOn() override; + Result torqueOff() override; + Result calibrateZeroQ(const std::string& joint_name) override; + Result emergencyStop() override; + Result protectiveStop() override { return emergencyStop(); } + Result setSpeedScaling(double scaling) override; + double getSpeedScaling() const override { return speed_scaling_; } + bool isProtectiveStopped() const override; + bool isEmergencyStopped() const override; + bool isFault() const override; + + Result moveJ(const JointPositionCommand& target, const MotionOptions& options) override; + Result speedJ(const JointVelocityCommand& velocity, double acceleration, double duration) override; + Result stopJ(double acceleration) override; + Result moveL(const CartesianPose& target, const MotionOptions& options, FrameType frame = FrameType::Base) override; + Result speedL(const CartesianVelocity& velocity, double acceleration, double duration, FrameType frame = FrameType::Base) override; + Result stopL(double acceleration) override; + Result stopMotion() override; + + Result startServoMode(const ServoOptions& options) override; + Result servoJ(const JointPositionCommand& target) override; + Result servoL(const CartesianPose& target, FrameType frame = FrameType::Base) override; + Result servoSpeedJ(const JointVelocityCommand& velocity) override; + Result servoSpeedL(const CartesianVelocity& velocity, FrameType frame = FrameType::Base) override; + Result stopServoMode() override; + + Result connect(const std::string& ip, int port) override; + Result disconnect() override; + bool isConnected() const override; + Result powerOn() override { return torqueOn(); } + Result powerOff() override { return torqueOff(); } + Result brakeRelease() override { return torqueOn(); } + Result shutdown() override; + Result clearFault() override; + Result unlockProtectiveStop() override { return clearFault(); } + Result loadProgram(const std::string& program_name) override; + Result playProgram() override; + Result pauseProgram() override; + Result stopProgram() override; + + std::vector ik(const std::string& base_link, + const std::string& ee_link, + const CartesianPose& pose) override; + std::shared_ptr kinematicsSolver() const override { return nullptr; } + CartesianPose fk(const std::string& base_link, const std::string& ee_link) override; + CartesianPose fk(bool is_tcp = true) override; + CartesianVelocity getSpeedLCommandTwistBase() const override; + bool busy() const override { return busy_.load(); } + +private: + struct HrState { + int moving{0}; + int enabled{0}; + int error{0}; + int error_code{0}; + int error_axis{0}; + int brake{0}; + int paused{0}; + int emergency_stop{0}; + int safeguard{0}; + int electrified{0}; + int connected_to_box{0}; + int blending_done{0}; + int in_pos{0}; + bool valid{false}; + }; + + Result ensureConnected_(const std::string& context) const; + Result unsupported_(const std::string& name) const; + Result hrResult_(int code, const std::string& context) const; + bool validDof_(std::size_t size, std::string& error) const; + HrState readHrState_() const; + std::vector readJointPositionRad_() const; + std::vector readJointVelocityRad_() const; + CartesianPose readTcpPose_() const; + CartesianVelocity readTcpVelocity_() const; + std::vector currentJointPositionDeg_() const; + std::string nextCommandId_() const; + Result waitMotionDone_(const std::string& context, int timeout_ms) const; + +private: + config::RobotArmConfig cfg_; + config::VendorRobotArmBackendConfig vendor_cfg_; + RobotModel model_; + std::string ip_; + int port_{10003}; + unsigned int box_id_{0}; + unsigned int robot_id_{0}; + std::string tcp_name_{"TCP"}; + std::string ucs_name_{"Base"}; + double speed_scaling_{1.0}; + std::atomic connected_{false}; + std::atomic busy_{false}; + std::atomic servo_mode_{false}; + mutable std::mutex mutex_; + mutable std::atomic command_seq_{0}; +}; + +} // namespace cmvr::device + +#endif // CMVR_ES_HUAYAN_ROBOT_H + + +#endif //CMVR_ES_HUAYAN_ARM_H \ No newline at end of file diff --git a/cmvr-es/devices/arm/robot_arm_factory.h b/cmvr-es/devices/arm/robot_arm_factory.h index bd73f19a..5f504392 100644 --- a/cmvr-es/devices/arm/robot_arm_factory.h +++ b/cmvr-es/devices/arm/robot_arm_factory.h @@ -7,6 +7,7 @@ #include "cmvr/config/arm_config/arm_config.pb.h" #include "common/base/logging/logger.h" #include "devices/arm/aubo_arm/include/aubo_arm.h" +#include "devices/arm/huayan_arm/huayan_arm.h" #include "devices/arm/motor_robot_arm/include/motor_robot_arm.h" namespace cmvr::device { @@ -25,6 +26,9 @@ public: if (vendor.brand() == config::VENDOR_ROBOT_ARM_BRAND_AUBO_ARM) { return std::make_shared(cfg); } + if (vendor.brand() == config::VENDOR_ROBOT_ARM_BRAND_HUAYAN_ARM) { + return std::make_shared(cfg); + } const std::string error = "[RobotArmFactory]: Vendor RobotArm is not implemented yet: " + cfg.id() + ", brand=" + config::VendorRobotArmBrand_Name(vendor.brand()); diff --git a/dependency/x86/third_party/huayan_arm/v1.0/include/HR_Pro.h b/dependency/x86/third_party/huayan_arm/v1.0/include/HR_Pro.h new file mode 100644 index 00000000..741155fe --- /dev/null +++ b/dependency/x86/third_party/huayan_arm/v1.0/include/HR_Pro.h @@ -0,0 +1,4528 @@ +/******************************************************************* + * Copyright(c) 2020-2025 + * All rights reserved. + * + * FileName:HR_Pro.h + * Descriptio:C++SDK + * version:1.0.15.0 + * Modification Records: + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * SDK Version Date Add Modify Delete + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.0.0 2022.05.12 Create a file + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.0.1 2023.02.17 HRIF_ReadOverride + * HRIF_ReadPointByName + * HRIF_SetFreeDriveMotionFreedom + * HRIF_SetPoseTrackingMaxMotionLimit + * HRIF_SetPoseTrackingPIDParams + * HRIF_SetPoseTrackingTargetPos + * HRIF_SetPoseTrackingState + * HRIF_SetUpdateTrackingPose + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.2.1 2023.03.21 HRIF_GetErrorCodeStr + * HRIF_MoveCAngle + * HRIF_MoveLinearWeave + * HRIF_MoveCircularWeave + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.3.1 2023.06.27 HRIF_EnterSafetyGuard + * HRIF_GetCircularViaPose + * HRIF_EndPushMovePath + * HRIF_ConfigTCP + * HRIF_ConfigUCS + * HRIF_MoveToSS + * HRIF_SetCollideLevel + * HRIF_ReadMaxPayload + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.4.0 2024.03.04 HRIF_SetSimulation + * HRIF_SetMoveParamsAO + * HRIF_CalUcsPlane + * HRIF_CalUcsLine + * HRIF_CalTcp3P + * HRIF_CalTcp4P + * HRIF_SetFreeDriveMotionFreedom + * HRIF_SetFTFreeFactor + * HRIF_SetTangentForceBounds + * HRIF_SetFreeDriveCompensateForce + * HRIF_InitPathJ + * HRIF_InitPathL + * HRIF_PushPathPoints + * HRIF_EndPushPathPoints + * HRIF_DelPath + * HRIF_ReadPathList + * HRIF_ReadPathInfo + * HRIF_UpdatePathName + * HRIF_ReadPathState + * HRIF_ReadTrackProcess + * HRIF_SpeedJ + * HRIF_SetPoseTrackingStopTimeOut + * HRIF_GetCollideLevel + * HRIF_SetECATAO + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.5.0 2024.03.11 HRIF_InitPath + * HRIF_EndPushMovePath + * HRIF_InitPathJ(Delete) + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.5.1 2024.03.25 HRIF_OpenBrake + * HRIF_CloseBrake + * HRIF_ReadBrakeStatus + * HRIF_ReadPayload + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.6.0 2024.04.09 HRIF_SetFTWrenchThresholds + * HRIF_SetMaxFreeDriveVel + * HRIF_ReadFTMotionFreedom + * HRIF_SetMaxSearchDistance + * HRIF_SetSteadyContactDeviationRange + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.6.1 2024.04.09 No changes + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.7.0 2024.09.18 HRIF_XToStandby + * HRIF_EnableEndBTN + * HRIF_cdsSetIO + * HRIF_ReadPointList + * HRIF_ReadTCPList + * HRIF_ReadUCSList + * HRIF_SetFTFreeDriveSpeedMode + * HRIF_ReadFTFreeDriveSpeedMode + * HRIF_MovePathJOL + * HRIF_GetMovePathJOLIndex + * HRIF_SpeedL + * HRIF_SpeedJ + * HRIF_ReadMaxRange + * HRIF_MoveAlignToZ + * HRIF_AddSafePlane + * HRIF_DelSafePlane + * HRIF_ReadSafePlaneList + * HRIF_ReadSafePlane + * HRIF_UpdateSafePlane + * HRIF_SetDepthThresholdForDampingArea + * HRIF_SetTriStageSwitch + * HRIF_ReadTriStageSwitch + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.8.0 2024.10.28 HRIF_SetJointMaxVel_nJ + * HRIF_SetJointMaxAcc_nJ + * HRIF_SetMaxAcsRange_nJ + * HRIF_ReadCmdJointPos_nJ + * HRIF_ReadCmdJointCur_nJ + * HRIF_ReadActJointCur_nJ + * HRIF_ReadActJointPos_nJ + * HRIF_ReadCmdJointVel_nJ + * HRIF_ReadActJointVel_nJ + * HRIF_GetInverseKin_nJ + * HRIF_GetForwardKin_nJ + * HRIF_WayPointRel_nJ + * HRIF_MoveJ_nJ + * HRIF_MoveL_nJ + * HRIF_MoveC_nJ + * HRIF_ReadJointMaxVel_nJ + * HRIF_ReadJointMaxJerk_nJ + * HRIF_ReadJointMaxAcc_nJ + * HRIF_ReadPointByName_nJ + * HRIF_ReadActPos_nJ + * HRIF_WayPointEx_nJ + * HRIF_WayPoint2_nJ + * HRIF_WayPoint_nJ + * HRIF_ReadAxisErrorCode_nJ + * HRIF_SwitchScript + * HRIF_ReadDefaultScript + * HRIF_EnterSafeGuard + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.9.0 2025.01.16 HRIF_ReadRobotType + * HRIF_ClearCoControlExpandAxisPos + * HRIF_SetRotationVelocityControlMode + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.10.0 2025.03.24 HRIF_SetBaseInstallingAngle + * HRIF_GetBaseInstallingAngle + * HRIF_GetLoadIdentifyResult + * HRIF_LoadIdentify + * HRIF_GetLastCalibParams + * HRIF_SetInitializeForceSensor + * HRIF_SetFTCalibration + * HRIF_ReadForceData + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.11.0 2025.03.25 HRIF_StartWeaveWeld + * HRIF_SetWeaveParams + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.12.0 2025.03.25 HRIF_LongMoveEvent + * HRIF_ShortJogJ + * HRIF_ShortJogL + * HRIF_LongJogJ + * HRIF_LongJogL + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.13.0 2025.04.15 HRIF_CalTCPOrt + * HRIF_SetDftTCP + * HRIF_ReadActCoord + * HRIF_ReadActCoord_nJ + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.14.0 2025.08.12 HRIF_GetInverseDynamic + * HRIF_SetPathRefJoints + * HRIF_ReadAllBoxCI + * HRIF_ReadAllBoxCO + * HRIF_ReadAllBoxDI + * HRIF_ReadAllBoxDO + * HRIF_SetFTUCS + * HRIF_ReadFTUCS + * HRIF_HRAppData + * HRIF_SetEndModbusInfo + * V1.0.14.1 2025.08.12 MoveC + * MoveC_nJ + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + * V1.0.15.0 2025.11.19 HRIF_MoveLByLockAxis_nJ + * HRIF_MoveCByLockAxis_nJ + * HRIF_WayPoint2ByLockAxis_nJ + * HRIF_SetLogDbgCB + * HRIF_SetEventCB + * -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + ***************************************************************************************************************************************/ + +#ifndef _HR_PRO_H_ +#define _HR_PRO_H_ + + +#include +#include +using namespace std; + +#define MaxBox 5 +#define MaxJointNums 15 + +#ifdef _MSC_VER // MSVC compiler + #ifdef ROBOT_PRO_EXPORTS + #define ROBOT_PRO_API __declspec(dllexport) + #else + #define ROBOT_PRO_API __declspec(dllimport) + #endif +#elif (__MINGW32__ || __MINGW64__) // MinGW compiler + #ifdef ROBOT_PRO_EXPORTS + #define ROBOT_PRO_API __attribute__((dllexport)) + #else + #define ROBOT_PRO_API __attribute__((dllimport)) + #endif +#else + #define ROBOT_PRO_API +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + + +//************************************************************************/ +//** Interfaces for initialization +//************************************************************************/ +/** + * @brief: Connect to port 10003 of CPS + * @param boxID : Control box ID, 0 as default + * @param hostName : IP address of CPS + * @param nPort : Port number, 10003 as default + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_Connect(unsigned int boxID, const char* hostName, unsigned short nPort); + +/** + * @brief: Disconnet from port 10003 of CPS + * @param boxID : Control box ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_DisConnect(unsigned int boxID); + +/** + * @brief: Check the connection status to CPS + * @param boxID : Control box ID, 0 as default + * @return : false : Disconnected + * true : Connected + */ +ROBOT_PRO_API bool HRIF_IsConnected(unsigned int boxID); + +/** + * @brief: Power off the robot and shut down the system + * @param boxID : Control box ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ShutdownRobot(unsigned int boxID); + +/** + * @brief: Connect to control box + * @param boxID : Control box ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_Connect2Box(unsigned int boxID); + +/** + * @brief: Power on the robot + * @param boxID : Control box ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_Electrify(unsigned int boxID); + +/** + * @brief: Power off the robot + * @param boxID : Control box ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_Blackout(unsigned int boxID); + +/** + * @brief: Connect to the controller, start master, initialize slave, configure and check paramters, finally switch to DISABLE state + * @param boxID : Control box ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_Connect2Controller(unsigned int boxID); + +/** + * @brief: Check the simulation status + * @param boxID : Control box ID, 0 as default + * @param nSimulateRobot :Simulation status + * 0 : real mode + * 1 : simulated mode + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_IsSimulateRobot(unsigned int boxID, int& nSimulateRobot); + +/** + * @brief: Check the simulation status + * @param boxID : Control box ID, 0 as default + * @param nSimulation :Simulation status + * 0 : real mode + * 1 : simulated mode + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetSimulation(unsigned int boxID, unsigned int rbtID, int nSimulation); + +/** + * @brief: Check the controller's state + * @param boxID : Control box ID, 0 as default + * @param nStarted :Start status + * 0 : not started + * 1 : started + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_IsControllerStarted(unsigned int boxID, int& nStarted); + +/** + * @brief: Read the version of robot + * Version consists of digits separated by '.' + * CPSVer.controlVer.BoxVerMajor.BoxVerMid.BoxVerMin.AlgorithmVer.ElfinFirmwareVer + + * @param rbtID : Robot ID, 0 as default + * @param strVer : Whole version + * @param nCPSVersion : CPS version + * @param nCodesysVersion : Controller version + * @param nBoxVerMajor : Control box version, 0 for simulated, 1~4 for real + * @param nBoxVerMid : Firmware version of control board + * @param nBoxVerMin : Firmware version of control board + * @param nAlgorithmVer : Algorithm version + * @param nElfinFirmwareVer : Firmware version + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadVersion(unsigned int boxID, unsigned int rbtID, string& strVer, int& nCPSVersion, int& nCodesysVersion, + int &nBoxVerMajor, int &nBoxVerMid, int &nBoxVerMin, + int &nAlgorithmVer, int &nElfinFirmwareVer); + +/** + * @brief: Get explanation of error code + * @param boxID : Control box ID, 0 as default + * @param nErrorCode : Error code + * @param strErrorMsg : Error code explanation + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GetErrorCodeStr(unsigned int boxID, int nErrorCode, string &strErrorMsg); +/** + * @brief: Read the robot model + * @param boxID : Control box ID, 0 as default + * @param strModel : Robot model + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadRobotModel(unsigned int boxID, string& strModel); + +/** + * @brief: Read the robot model + * @param boxID : Control box ID, 0 as default + * @param RobotType : Robot Type + * @param MovelSet: MovelSet + * @param Axist6Type: The Sixth axis type + * @param Axist7Type: The Seventh axis type + * @param Axist8Type: The Eighth axis type + * @param Axist9Type: The Ninth axis type + * @param Axist10Type: The Tenth axis type + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadRobotType(unsigned int boxID, int& RobotType,string& MovelSet,int& Axist6Type,int& Axist7Type,int& Axist8Type,int& Axist9Type,int& Axist10Type); + + +//************************************************************************/ +//** Axis group control command interface +//************************************************************************/ +/** + * @brief: Enable the robot + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GrpEnable(unsigned int boxID, unsigned int rbtID); + +/** + * @brief: Disable the robot + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GrpDisable(unsigned int boxID, unsigned int rbtID); + +/** + * @brief: Reset the robot + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GrpReset(unsigned int boxID, unsigned int rbtID); + +/** + * @brief: Stop robot moving + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GrpStop(unsigned int boxID, unsigned int rbtID); + +/** + * @brief: Pause robot moving + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GrpInterrupt(unsigned int boxID, unsigned int rbtID); + +/** + * @brief: Continue robot moving after pause + * @param boxID : Control box number, 0 as default + * @param rbtID : Robot ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GrpContinue(unsigned int boxID, unsigned int rbtID); + +/** + * @brief: Open free driver + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GrpOpenFreeDriver(unsigned int boxID, unsigned int rbtID); + +/** + * @brief: Close free driver + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GrpCloseFreeDriver(unsigned int boxID, unsigned int rbtID); + +/** + * @brief: Open the brake + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nAxisID : Robot Axis ID,0-5 + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_OpenBrake(unsigned int boxID, unsigned int rbtID, int nAxisID); + +/** + * @brief: Close the brake + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nAxisID : Robot Axis ID,0-5 + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_CloseBrake(unsigned int boxID, unsigned int rbtID, int nAxisID); + +/** + * @brief: Read the brake status of each Axis + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param stateJ1~J6 : Robot Axis status + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadBrakeStatus(unsigned int boxID,unsigned int rbtID,int& stateJ1,int& stateJ2,int& stateJ3,int& stateJ4,int& stateJ5,int& stateJ6); + +/** + * @brief: Read whether the three-stage switch is turned on and the mode + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nThreeStageEnable : Three stage switch + * 0:colse + * 1:open + * @param nThreeStageMode : Mode + * 0:FreeDrive + * 1:Enable + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadTriStageSwitch(unsigned int boxID, unsigned int rbtID,int& nThreeStageEnable, int& nThreeStageMode); + +/** + * @brief: Set three-stage switch and mode + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nThreeStageEnable : Three stage switch + * 0:colse + * 1:open + * @param nThreeStageMode : Mode + * 0:FreeDrive + * 1:Enable + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetTriStageSwitch(unsigned int boxID, unsigned int rbtID, int nThreeStageEnable, int nThreeStageMode); + +/** + * @brief: Z-axis alignment + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param TcpName : Tcp Name + * @param UcsName : Ucs Name + * @param bIsReached : Is the Z-axis aligned + * @param dbJ1-dbJ6 : J1-J6 Joint Position + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveAlignToZ(unsigned int boxID, unsigned int rbtID, string TcpName, string UcsName,bool &bIsReached,double &dbJ1,double &dbJ2,double &dbJ3,double &dbJ4,double &dbJ5,double &dbJ6); + +//************************************************************************/ +//** Interfaces for script +//************************************************************************/ +/** + * @brief: Run the specified function + * @param boxID : Control box ID, 0 as default + * @param strFuncName : The specified function name composed in the teaching pendant + * @param param : Parameters + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_RunFunc(unsigned int boxID, string strFuncName, vector param); + +/** + * @brief: Start a task to bring the robot to Standby state from other states, replacing other multiple commands. After the task is launched, the system checks the current state and sends related commands automaticallly. If the system can't return to Standby state within 30s, the task will end. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @return : nRet=0 : Task launched successfully + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_XToStandby(unsigned int boxID, unsigned int rbtID); + +/** + * @brief: Run the main function to start the script execution composed and compiled in the teaching pendant + * @param boxID : Control box ID, 0 as default + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_StartScript(unsigned int boxID); + +/** + * @brief: Stop the script execution running in teaching pendant + * @param boxID : Control box ID, 0 as default + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_StopScript(unsigned int boxID); + +/** + * @brief: Pause the script execution running in teaching pendant + * @param boxID : Control box ID, 0 as default + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_PauseScript(unsigned int boxID); + +/** + * @brief:Continue the script execution paused int the teaching pendant + * @param rbtID : Robot ID, 0 as default + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ContinueScript(unsigned int boxID); + +/** + * @brief:Applies the specified script file + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param strScriptName : ScriptName + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SwitchScript(unsigned int boxID, unsigned int rbtID, string strScriptName); + +/** + * @brief:Read the current application script file + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param strScriptName : ScriptName + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadDefaultScript(unsigned int boxID, unsigned int rbtID, string &strScriptName); + +//************************************************************************/ +//** Interfaces for control box +//************************************************************************/ +/** + * @brief: Read box message + * @param boxID : Control box ID, 0 as default + * @param rbtID : robot ID, 0 as default + * @param nConnected : Control box connection status + * @param n48V_ON : Voltage value with 48V + * @param d48OUT_Voltag : 48V output voltage + * @param d48OUT_Current : 48V output current + * @param nRemoteBTN : Remote emergency stop status + * @param nThreeStageBTN : Three stage status + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadBoxInfo(unsigned int boxID, unsigned int rbtID, + int &nConnected, + int &n48V_ON, + double &d48OUT_Voltag, + double &d48OUT_Current, + int &nRemoteBTN, + int &nThreeStageBTN); + +/** + * @brief: Read box control input + * @param boxID : Control box ID, 0 as default + * @param nBit : CI bit + * @param nVal : CI value + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadBoxCI(unsigned int boxID, int nBit, int& nVal); + +/** + * @brief: Read box digital input + * @param boxID : Control box ID, 0 as default + * @param nBit : DI bit + * @param nVal : DI value + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadBoxDI(unsigned int boxID, int nBit, int& nVal); + +/** + * @brief: Read box control output + * @param boxID : Control box ID, 0 as default + * @param nBit : CO bit + * @param nVal : CO value + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadBoxCO(unsigned int boxID, int nBit, int& nVal); + +/** + * @brief: Read box digital output + * @param boxID : Control box ID, 0 as default + * @param nBit : DO bit + * @param nVal : DO value + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadBoxDO(unsigned int boxID, int nBit, int& nVal); + +/** + * @brief: Read all control box config digital input statuses + * @param boxID : Control box ID, 0 by default + * @param Val : Vector storing all config input (CI) values + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Function call error code + */ +ROBOT_PRO_API int HRIF_ReadAllBoxCI(unsigned int boxID, vector& Val); + +/** + * @brief: Read all control box general digital input statuses + * @param boxID : Control box ID, 0 by default + * @param Val : Vector storing all digital input (DI) values + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Function call error code + */ +ROBOT_PRO_API int HRIF_ReadAllBoxDI(unsigned int boxID, vector& Val); + +/** + * @brief: Read all control box config digital output statuses + * @param boxID : Control box ID, 0 by default + * @param Val : Vector storing all config output (CO) values + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Function call error code + */ +ROBOT_PRO_API int HRIF_ReadAllBoxCO(unsigned int boxID, vector& Val); + +/** + * @brief: Read all control box general digital output statuses + * @param boxID : Control box ID, 0 by default + * @param Val : Vector storing all digital output (DO) values + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Function call error code + */ +ROBOT_PRO_API int HRIF_ReadAllBoxDO(unsigned int boxID, vector& Val); + +/** + * @brief: Read box analog input + * @param boxID : Control box ID, 0 as default + * @param nBit : AI bit + * @param dbVal : Current (4~20mA) or voltage (0~10V) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadBoxAI(unsigned int boxID, int nBit, double& dbVal); + +/** + * @brief: Read box analog output + * @param boxID : Control box ID, 0 as default + * @param nBit : AO bit + * @param nMode : Current or voltage mode + * @param dbVal : Current value (4~20mA) or voltage value (0~10V) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadBoxAO(unsigned int boxID, int nBit, int& nMode, double& dbVal); + +/** + * @brief: Set Box control output + * @param boxID : Control box ID, 0 as default + * @param nBit : CO bit + * @param nVal : CO value + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetBoxCO(unsigned int boxID, int nBit, int nVal); + +/** + * @brief: Set Box digital output + * @param boxID : Control box ID, 0 as default + * @param nBit : DO bit + * @param nVal : DO value + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetBoxDO(unsigned int boxID, int nBit, int nVal); + +/** + * @brief: Set Box analog output mode + * @param boxID : Control box ID, 0 as default + * @param nBit : AO bit + * @param nMode : AO mode + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetBoxAOMode(unsigned int boxID, int nBit, int nMode); + +/** + * @brief: Set Box analog output + * @param boxID : Control box ID, 0 as default + * @param nBit : AO bit + * @param dbVal : AO value + * @param nMode : AO mode + + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetBoxAOVal(unsigned int boxID, int nBit, double dbVal, int nMode); + +/** + * @brief: Read End digital input + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nBit : End DI bit + * @param nVal : End DI value + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadEndDI(unsigned int boxID, unsigned int rbtID, int& nBit, int& nVal); + +/** + * @brief: Read End digital output + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nBit : End DO bit + * @param nVal : End DO value + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadEndDO(unsigned int boxID, unsigned int rbtID, int& nBit, int& nVal); + +/** + * @brief: Set End digital output + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nBit : End DO bit + * @param nVal : End DO value + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetEndDO(unsigned int boxID, unsigned int rbtID, int nBit, int nVal); + +/** + * @brief: Read End analog input + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nBit : End AI bit + * @param nVal : End AI value + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadEndAI(unsigned int boxID, unsigned int rbtID, int& nBit, double& dVal); + +/** + * @brief: Read End Button + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nBit1 : End button 1 + * @param nBit2 : End button 2 + * @param nBit3 : End button 3 + * @param nBit4 : End button 4 + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadEndBTN(unsigned int boxID, unsigned int rbtID, int& nBit1, int& nBit2, int& nBit3, int& nBit4); + +/** + * @brief: Set End Button + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nStatus : End button status, 1 for enabled, 0 for enabled + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_EnableEndBTN(unsigned int boxID, unsigned int rbtID, int nStatus); + +/** + * @brief: Set IO before the motion command reaches the target point + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nEndDOMask : The EndDO to be modified is identified by bit + * @param nEndDOVal : The target state of each EndDO to be modified. + * @param nBoxDOMask : The BoxDO to be modified is identified by bit. + * @param nBoxDOVal : The target state of each BoxDO to be modified. + * @param nBoxCOMask : The BoxCO to be modified is identified by bit + * @param nBoxCOVal : The target state of each BoxCO to be modified + * @param nBoxAOCH0_Mask : Indicator of whether BoxAOCH0 needs to be modified. + * @param nBoxAOCH0_Mode : Mode + * @param nBoxAOCH1_Mask : Indicator of whether BoxAOCH1 needs to be modified. + * @param nBoxAOCH1_Mode : Mode + * @param dbBoxAOCH0_Val : Corresponding analog value + * @param dbBoxAOCH1_Val : Corresponding analog value + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_cdsSetIO(unsigned int boxID, unsigned int rbtID, int nEndDOMask, int nEndDOVal,int nBoxDOMask,int nBoxDOVal,int nBoxCOMask,int nBoxCOVal,int nBoxAOCH0_Mask,int nBoxAOCH0_Mode,int nBoxAOCH1_Mask,int nBoxAOCH1_Mode,double dbBoxAOCH0_Val,double dbBoxAOCH1_Val); + +////////////////////////////////////////////////////////////////////////////////////// +/** + * @brief: Set Override + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dbOverride : Override value (0.01~1.00) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetOverride(unsigned int boxID, unsigned int rbtID, double dOverride); + +/** + * @brief: Read Override + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dbOverride : Override value + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadOverride(unsigned int boxID, unsigned int rbtID, double &dOverride); + +/** + * @brief: Set Tool motion + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nState : 0 for closed, 1 for open + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetToolMotion(unsigned int boxID, unsigned int rbtID, int nState); + +/** + * @brief: Read the maximum payload + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dbMaxPayload : maximum payload at the end (kg) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadMaxPayload(unsigned int boxID, unsigned int rbtID, double& dbMaxPayload); + +/** + * @brief: Set payload + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param Mass:Mass + * @param dX:Gravity-CX + * @param dY:Gravity-CY + * @param dZ:Gravity-CZ + * @param bUpdateDB:Optional parameters,default to 1 if not written + * 1:Modify payload,modify mass,write to database + * 3:Modify payload,not modify mass,write to database + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetPayload(unsigned int boxID, unsigned int rbtID, double dMass, double dX, double dY, double dZ, unsigned char byOption=1); + +/** + * @brief: Read payload + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param Mass:Mass + * @param dX:Gravity-CX + * @param dY:Gravity-CY + * @param dZ:Gravity-CZ + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadPayload(unsigned int boxID, unsigned int rbtID, double& dMass, double& dX, double& dY, double& dZ); + +// Motion speed: mm/s or °/s ,Motion acceleration: mm/(s^2) or °/(s^2) +/** + * @brief: Set joint max velocity + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dJ1~6 : Joint1~6 max velocity [°/ s] + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetJointMaxVel(unsigned int boxID, unsigned int rbtID, double dJ1, double dJ2, double dJ3, double dJ4, double dJ5, double dJ6); + +/** + * @brief: Set joint max acceleration + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dJ1~6 : Joint1~6 max acceleration [°/ s^2] + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetJointMaxAcc(unsigned int boxID, unsigned int rbtID, double dJ1, double dJ2, double dJ3, double dJ4, double dJ5, double dJ6); + +/** + * @brief: Set linear max velocity + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dMaxVel : Linear max velocity + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetLinearMaxVel(unsigned int boxID, unsigned int rbtID, double dMaxVel); + +/** + * @brief: Set linear max acceleration + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dMaxAcc : Linear max acceleration + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetLinearMaxAcc(unsigned int boxID, unsigned int rbtID, double dMaxAcc); + +/** + * @brief: Set max range of joint motion + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dMaxJ1-dMaxJ6 : Max joint angle + * @param dMinJ1-dMinJ6 : Min joint angle + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetMaxAcsRange(unsigned int boxID, unsigned int rbtID, + double dMaxJ1, double dMaxJ2, double dMaxJ3, double dMaxJ4, double dMaxJ5, double dMaxJ6, + double dMinJ1, double dMinJ2, double dMinJ3, double dMinJ4, double dMinJ5, double dMinJ6); + +/** + * @brief: Set max range of linear motion + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dMaxX-dMaxZ : Max range of XYZ + * @param dMinX-dMinZ : Min range of XYZ + * @param dUcs_X~Rz : UCS pose + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetMaxPcsRange(unsigned int boxID, unsigned int rbtID, + double dMaxX, double dMaxY, double dMaxZ, double dMinX, double dMinY, double dMinZ, + double dUcs_X, double dUcs_Y, double dUcs_Z, double dUcs_Rx, double dUcs_Ry, double dUcs_Rz); + +/** + * @brief: Set max range of linear motion + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nMode : Whether to enable the speed limit constraint on the change rate of orientation angles for MoveC/L commands. + * 0 : Restricted (default) mode + * 1 : Unrestricted mode + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetRotationVelocityControlMode(unsigned int boxID, unsigned int rbtID,int nMode); + +/** + * @brief: Read joint range and linear range + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dMaxJ1-dMaxJ6 : Max joint angle + * @param dMinJ1-dMinJ6 : Min joint angle + * @param dMaxX-dMaxRZ : Max range of XYZRXRYRZ + * @param dMinX-dMinRZ : Min range of XYZRXRYRZ + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadMaxRange(unsigned int boxID, unsigned int rbtID,vector &MaxJointRange,vector &MinJointRange,vector &MaxLinerRange,vector &MinLinerRange); + +/** + * @brief: Read joint range and linear range + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dMaxJ1-dMaxJ6 : Max joint angle + * @param dMinJ1-dMinJ6 : Min joint angle + * @param dMaxX-dMaxRZ : Max range of XYZRXRYRZ + * @param dMinX-dMinRZ : Min range of XYZRXRYRZ + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadMaxRange_nJ(unsigned int boxID, unsigned int rbtID,vector &MaxJointRange,vector &MinJointRange,vector &MaxLinerRange,vector &MinLinerRange); + +/** + * @brief: Set welding analog output voltage + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nState : Channel switch status. 0:off 1:open + * @param nIndex : Voltage analog channel: AO0 and AO1 + * @param dInitAO : Initial voltage + * @param dWeldingAO : Welding voltage. Unit: V + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetMoveParamsAO(unsigned int boxID, unsigned int rbtID, + int nState, int nIndex, double dInitAO, double dWeldingAO); + +/** + * @brief: Read joint max velocity + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dJ1~6 : Max velocity of J1~J6 + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadJointMaxVel(unsigned int boxID, unsigned int rbtID, double& dJ1, double& dJ2, double& dJ3, double& dJ4, double& dJ5, double& dJ6); + +/** + * @brief: Read joint max acceleration + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dJ1~6 : Max acceleration of J1~J6 + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadJointMaxAcc(unsigned int boxID, unsigned int rbtID, double& dJ1, double& dJ2, double& dJ3, double& dJ4, double& dJ5, double& dJ6); + +/** + * @brief: Read joint max jerk + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dJ1~6 : Max jerk of J1~J6 + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadJointMaxJerk(unsigned int boxID, unsigned int rbtID, double& dJ1, double& dJ2, double& dJ3, double& dJ4, double& dJ5, double& dJ6); + +/** + * @brief:Read linear motion parameters + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dMaxVel : Linear motion velocity + * @param dMaxAcc : Linear motion acceleration + * @param dMaxJerk : Linear motion jerk + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadLinearMaxSpeed(unsigned int boxID, unsigned int rbtID, double& dMaxVel, double& dMaxAcc, double& dMaxJerk); + +////////////////////////////////////////////////////////////////////////////////////// +/** + * @brief: Read Emergency stop information + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nESTO_Error: Error occurs when the two emergency signals are different + * @param nESTO: Cut off 48V output to the robot when an emergency stop occurs + * @param nSafeGuard_Error: Error occurs when the two safeguard signals are different + * @param nSafeGuard:Stop the motion but never cutt off power supply when safeguard occurs + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadEmergencyInfo(unsigned int boxID, unsigned int rbtID, + int &nESTO_Error, + int &nESTO, + int &nSafeGuard_Error, + int &nSafeGuard); + +/** + * @brief: Enter or exit safeguard status by force to realize soft EStop(emergency stop). Move will be stopped. System can be reset after EStop is canceled. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nFlag:1 to enter, 0 to cancel + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_EnterSafetyGuard(unsigned int boxID, unsigned int rbtID, int nFlag); + +/** + * @brief: Enter or exit safeguard status by force to realize soft EStop(emergency stop). Move will be stopped. System can be reset after EStop is canceled. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nFlag:1 to enter, 0 to cancel + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_EnterSafeGuard(unsigned int boxID, unsigned int rbtID, int nFlag); + +/** + * @brief: Read robot state + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nMovingState : Moving state + * @param nEnableState : Enable state + * @param nErrorState : Error state + * @param nErrorCode : Error code + * @param nErrorAxis : Error axis ID + * @param nBreaking : Breaking status + * @param nPause : Pause state + * @param nEmergencyStop : Emergency stop state + * @param nSafeGuard : Safty guard state + * @param nElectrify : Electrify state + * @param nIsConnectToBox : Connection of control box state + * @param nBlendingDone : Moving blending done state + * @param nInPos : In actual pose state + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadRobotState(unsigned int boxID, unsigned int rbtID, + int &nMovingState, + int &nEnableState, + int &nErrorState, + int &nErrorCode, + int &nErrorAxis, + int &nBreaking, + int &nPause, + int &nEmergencyStop, + int &nSafeGuard, + int &nElectrify, + int &nIsConnectToBox, + int &nBlendingDone, + int &nInpos); + +/** + * @brief: Read robot state + * @param boxID : control box number, 0 as default + * @param rbtID : robot ID, 0 as default + * @param nMovingState : Moving state + * @param nEnableState : Enable state + * @param nErrorState : Error state + * @param nErrorCode : Error code + * @param nErrorAxis : Error axis ID + * @param nBreaking : Breaking status + * @param nPause : Pause state + * @param nBlendingDone : Moving blending done state + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadRobotFlags(unsigned int boxID, unsigned int rbtID, + int &nMovingState, + int &nEnableState, + int &nErrorState, + int &nErrorCode, + int &nErrorAxis, + int &nBreaking, + int &nPause, + int &nBlendingDone); +/** + * @brief: Read current waypoint ID + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param strCurWaypointID : Current ID + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadCurWaypointID(unsigned int boxID, unsigned int rbtID, string& strCurWaypointID); + +/** + * @brief: Read axis error code + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nErrorCode : Error code + * @param Params : The error code of J1~J6, 0 if no error + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadAxisErrorCode(unsigned int boxID, unsigned int rbtID, int& nErrorCode, int& nJ1, int& nJ2, int& nJ3, int& nJ4, int& nJ5, int& nJ6); + +/** + * @brief: Read current FSM + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nCurFSM : Current FSM + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadCurFSM(unsigned int boxID, unsigned int rbtID, int& nCurFSM, string& strCurFSM); + +/** + * @brief: Read FSM from CPS + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nCurFSM : Current FSM + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadCurFSMFromCPS(unsigned int boxID, unsigned int rbtID, int& nCurFSM); + +/** + * @brief: Read actual pose and joint positions + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz : pose + * @param dJ1~6 : joint positions + * @param dTcp_X~Rz : TCP pose + * @param dUcs_X~Rz : UCS pose + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadActPos(unsigned int boxID, unsigned int rbtID, + double &dX, double &dY, double &dZ, double &dRx, double &dRy, double &dRz, + double &dJ1, double &dJ2, double &dJ3, double &dJ4, double &dJ5, double &dJ6, + double &dTcp_X, double &dTcp_Y, double &dTcp_Z, double &dTcp_Rx, double &dTcp_Ry, double &dTcp_Rz, + double &dUcs_X, double &dUcs_Y, double &dUcs_Z, double &dUcs_Rx, double &dUcs_Ry, double &dUcs_Rz); + +/** + * @brief: Read point pose, joint positions, UCS and TCP by point name + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dJ1~6 : joint positions + * @param dX~Rz : pose + * @param dTcp_X~Rz : TCP pose + * @param dUcs_X~Rz : UCS pose + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadPointByName(unsigned int boxID, unsigned int rbtID, string strPointName, + double& dJ1, double& dJ2, double& dJ3, double& dJ4, double& dJ5, double& dJ6, + double& dX, double& dY, double& dZ, double& dRx, double& dRy, double& dRz, + double& dTcp_X, double& dTcp_Y, double& dTcp_Z, double& dTcp_Rx, double& dTcp_Ry, double& dTcp_Rz, + double& dUcs_X, double& dUcs_Y, double& dUcs_Z, double& dUcs_Rx, double& dUcs_Ry, double& dUcs_Rz); + +/** + * @brief: Read point list as name + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param PointList : point list + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadPointList(unsigned int boxID, unsigned int rbtID, vector &PointList); + +/** + * @brief: Read command joint positions + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dJ1~6 : Joint command positions + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadCmdJointPos(unsigned int boxID, unsigned int rbtID, double& dJ1, double& dJ2, double& dJ3, double& dJ4, double& dJ5, double& dJ6); + +/** + * @brief: Read actual joint positions + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dJ1~6 : Joint actual positions + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadActJointPos(unsigned int boxID, unsigned int rbtID, double& dJ1, double& dJ2, double& dJ3, double& dJ4, double& dJ5, double& dJ6); + +/** + * @brief: Read command pose of TCP + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz : TCP command pose + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadCmdTcpPos(unsigned int boxID, unsigned int rbtID, double& dX, double& dY, double& dZ, double& dRx, double& dRy, double& dRz); + +/** + * @brief: Read actual pose of TCP + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz : TCP actual pose + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadActTcpPos(unsigned int boxID, unsigned int rbtID, double& dX, double& dY, double& dZ, double& dRx, double& dRy, double& dRz); + +/** + * @brief: Read command velocity of joint + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dJ1~6 : Joint command velocity + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadCmdJointVel(unsigned int boxID, unsigned int rbtID, double& dJ1, double& dJ2, double& dJ3, double& dJ4, double& dJ5, double& dJ6); + +/** + * @brief: Read actual velocity of joint + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dJ1~6 : Joint actual velocity + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadActJointVel(unsigned int boxID, unsigned int rbtID, double& dJ1, double& dJ2, double& dJ3, double& dJ4, double& dJ5, double& dJ6); + +/** + * @brief: Read command velocity of TCP + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz : TCP command velocity + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadCmdTcpVel(unsigned int boxID, unsigned int rbtID, double& dX, double& dY, double& dZ, double& dRx, double& dRy, double& dRz); + +/** + * @brief: Read actual velocity of TCP + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz : TCP actual velocity + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadActTcpVel(unsigned int boxID, unsigned int rbtID, double& dX, double& dY, double& dZ, double& dRx, double& dRy, double& dRz); + +/** + * @brief: Read command current of joint + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dJ1~6 : Joint command current + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadCmdJointCur(unsigned int boxID, unsigned int rbtID, double& dJ1, double& dJ2, double& dJ3, double& dJ4, double& dJ5, double& dJ6); + +/** + * @brief: Read actual current of joint + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dJ1~6 : Joint actual current + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadActJointCur(unsigned int boxID, unsigned int rbtID, double& dJ1, double& dJ2, double& dJ3, double& dJ4, double& dJ5, double& dJ6); + +/** + * @brief: Read End velocity of TCP + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dCmdVel : TCP command velocity mm/s + * @param dActVel : TCP actual velocity mm/s + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadTcpVelocity(unsigned int boxID, unsigned int rbtID, double& dCmdVel, double& dActVel); + +/** + * @brief: Set reduce mode + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nReduceMode : Reduce mode, 0 to exit reduce mode, 1 to entry reduce mode + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetReduceMode(unsigned int boxID, unsigned int rbtID, int nReduceMode); + +/** + * @brief: Read reduce mode + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nReduceMode : Current reduce mode, 0 for not reduce, 1 for reduce mode + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function cal + */ +ROBOT_PRO_API int HRIF_ReadReduceMode(unsigned int boxID, unsigned int rbtID, int& nReduceMode); + + +//************************************************************************/ +//** Interfaces for calculation +//************************************************************************/ +/** + * @brief: Quaternion to Euler angle (°) + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dQuaW : Quaternion W + * @param dQuaX : Quaternion X + * @param dQuaY : Quaternion Y + * @param dQuaZ : Quaternion Z + * @param dRx: Euler angle RX + * @param rRy: Euler angle RY + * @param dRz: Euler angle RZ + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_Quaternion2RPY(unsigned int boxID, unsigned int rbtID, double dQuaW, double dQuaX, double dQuaY, double dQuaZ, double& dRx, double& dRy, double& dRz); + +/** + * @brief: Euler angle to Quaternion (°) + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dRx: Euler angle RX + * @param dRy: Euler angle RY + * @param dRz: Euler angle RZ + * @param dQuaW : Quaternion W + * @param dQuaX : Quaternion X + * @param dQuaY : Quaternion Y + * @param dQuaZ : Quaternion Z + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_RPY2Quaternion(unsigned int boxID, unsigned int rbtID, double dRx, double dRy, double dRz, double& dQuaW, double& dQuaX, double& dQuaY, double& dQuaZ); +/** + * @brief: Inverse kinematics transformation from pose to joint positions + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dCoord_X~Rz : pose in specified UCS and TCP + * @param dTcp_X~Rz : TCP pose + * @param dUcs_X~Rz : UCS pose + * @param dJ1~6 : Reference joint positions used for the selection in multiple solutions + * @param dTargetJ1~6 : Inverse solution of J1~J6 + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GetInverseKin(unsigned int boxID, unsigned int rbtID, double dCoord_X, double dCoord_Y, double dCoord_Z, double dCoord_Rx, double dCoord_Ry, double dCoord_Rz, + double dTcp_X, double dTcp_Y, double dTcp_Z, double dTcp_Rx, double dTcp_Ry, double dTcp_Rz, + double dUcs_X, double dUcs_Y, double dUcs_Z, double dUcs_Rx, double dUcs_Ry, double dUcs_Rz, + double dJ1, double dJ2, double dJ3, double dJ4, double dJ5, double dJ6, + double &dTargetJ1, double &dTargetJ2, double &dTargetJ3, double &dTargetJ4, double &dTargetJ5, double &dTargetJ6); + +/** + * @brief: Forward kinematics transformation from joint positions to pose + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dJ1~6 : joint positions + * @param dTcp_X~Rz : TCP pose for the target + * @param dUcs_X~Rz : UCS pose for the target + * @param dTarget_X~Rz : pose in specified UCS and TCP + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GetForwardKin(unsigned int boxID, unsigned int rbtID, double dJ1, double dJ2, double dJ3, double dJ4, double dJ5, double dJ6, + double dTcp_X, double dTcp_Y, double dTcp_Z, double dTcp_Rx, double dTcp_Ry, double dTcp_Rz, + double dUcs_X, double dUcs_Y, double dUcs_Z, double dUcs_Rx, double dUcs_Ry, double dUcs_Rz, + double &dTarget_X, double &dTarget_Y, double &dTarget_Z, double &dTarget_Rx, double &dTarget_Ry, double &dTarget_Rz); + +/*** + * @brief: Calculate inverse dynamics results + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dPose_J1~6 : Joint position (°) + * @param dVel_J1~6 : Joint velocities (°/s) + * @param dAcc_J1~6 : Joint accelerations (°/s²) + * @param dTorq_J1~6 : Joint torques calculated by inverse dynamics (Nm) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GetInverseDynamics(unsigned int boxID, unsigned int rbtID, double dPose_J1, double dPose_J2, double dPose_J3, double dPose_J4, double dPose_J5, double dPose_J6, + double dVel_J1,double dVel_J2,double dVel_J3,double dVel_J4,double dVel_J5,double dVel_J6, + double dAcc_J1,double dAcc_J2,double dAcc_J3,double dAcc_J4,double dAcc_J5,double dAcc_J6, + double& dTorq_J1,double& dTorq_J2,double& dTorq_J3,double& dTorq_J4,double& dTorq_J5,double& dTorq_J6); + +/** + * @brief: Pose transformation from Base to UCS&TCP + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dCoord_X~Rz : pose in Base coordinate system + * @param dTcp_X~Rz : TCP pose for the target + * @param dUcs_X~Rz : UCS pose for the target + * @param dTarget_X~Rz : pose in the specified UCS and TCP + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_Base2UcsTcp(unsigned int boxID, unsigned int rbtID, double dCoord_X, double dCoord_Y, double dCoord_Z, double dCoord_Rx, double dCoord_Ry, double dCoord_Rz, + double dTcp_X, double dTcp_Y, double dTcp_Z, double dTcp_Rx, double dTcp_Ry, double dTcp_Rz, + double dUcs_X, double dUcs_Y, double dUcs_Z, double dUcs_Rx, double dUcs_Ry, double dUcs_Rz, + double &dTarget_X, double &dTarget_Y, double &dTarget_Z, double &dTarget_Rx, double &dTarget_Ry, double &dTarget_Rz); + +/** + * @brief: Pose transformation from UCS&TCP to Base + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dCoord_X~Rz : pose in speicified UCS and TCP + * @param dTcp_X~Rz : TCP pose for dCoord + * @param dUcs_X~Rz : UCS pose for dCoord + * @param dTarget_X~Rz : pose in Base coordinate system + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_UcsTcp2Base(unsigned int boxID, unsigned int rbtID, double dCoord_X, double dCoord_Y, double dCoord_Z, double dCoord_Rx, double dCoord_Ry, double dCoord_Rz, + double dTcp_X, double dTcp_Y, double dTcp_Z, double dTcp_Rx, double dTcp_Ry, double dTcp_Rz, + double dUcs_X, double dUcs_Y, double dUcs_Z, double dUcs_Rx, double dUcs_Ry, double dUcs_Rz, + double &dTarget_X, double &dTarget_Y, double &dTarget_Z, double &dTarget_Rx, double &dTarget_Ry, double &dTarget_Rz); + +/** + * @brief: Pose addition calculation + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dPose1_X~Rz : Pose of point 1 + * @param dPose2_X~Rz : Pose of point 2 + * @param dPose3_X~Rz : Calculation result + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_PoseAdd(unsigned int boxID, unsigned int rbtID, double dPose1_X, double dPose1_Y, double dPose1_Z, double dPose1_Rx, double dPose1_Ry, double dPose1_Rz, + double dPose2_X,double dPose2_Y,double dPose2_Z,double dPose2_Rx,double dPose2_Ry,double dPose2_Rz, + double &dPose3_X,double &dPose3_Y,double &dPose3_Z,double &dPose3_Rx,double &dPose3_Ry,double &dPose3_Rz); + +/** + * @brief: Pose subtraction calculation + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dPose1_X~Rz : Pose of point 1 + * @param dPose2_X~Rz : Pose of point 2 + * @param dPose3_X~Rz : Calculation result + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_PoseSub(unsigned int boxID, unsigned int rbtID, double dPose1_X, double dPose1_Y, double dPose1_Z, double dPose1_Rx, double dPose1_Ry, double dPose1_Rz, + double dPose2_X,double dPose2_Y,double dPose2_Z,double dPose2_Rx,double dPose2_Ry,double dPose2_Rz, + double &dPose3_X,double &dPose3_Y,double &dPose3_Z,double &dPose3_Rx,double &dPose3_Ry,double &dPose3_Rz); + +/** + * @brief: Pose transformation through combined caculations, from P1 based on Base coordinate system to P3 based on UCS with P2 + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dPose1_X~Rz : Pose of P1 + * @param dPose2_X~Rz : Pose of P2 + * @param dPose3_X~Rz : Calculation result, P3 + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_PoseTrans(unsigned int boxID, unsigned int rbtID, double dPose1_X, double dPose1_Y, double dPose1_Z, double dPose1_Rx, double dPose1_Ry, double dPose1_Rz, + double dPose2_X,double dPose2_Y,double dPose2_Z,double dPose2_Rx,double dPose2_Ry,double dPose2_Rz, + double &dPose3_X,double &dPose3_Y,double &dPose3_Z,double &dPose3_Rx,double &dPose3_Ry,double &dPose3_Rz); + +/** + * @brief: Inverse kinematics transformation for pose + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dPoseFrom_X~Rz : Pose of oringinal point + * @param dPoseTo_X~Rz : Pose of calculation result + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_PoseInverse(unsigned int boxID, unsigned int rbtID, double dPoseFrom_X, double dPoseFrom_Y, double dPoseFrom_Z, double dPoseFrom_Rx, double dPoseFrom_Ry, double dPoseFrom_Rz, + double &dPoseTo_X,double &dPoseTo_Y,double &dPoseTo_Z,double &dPoseTo_Rx,double &dPoseTo_Ry,double &dPoseTo_Rz); + +/** + * @brief: Calculate points distance + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dPose1_X~Rz : Pose of point 1 + * @param dPose2_X~Rz : Pose of point 2 + * @param dDistance : Points distance (mm) + * @param dAngle : Angle between poses (°) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_PoseDist(unsigned int boxID, unsigned int rbtID, double dPose1_X, double dPose1_Y, double dPose1_Z, double dPose1_Rx, double dPose1_Ry, double dPose1_Rz, + double dPose2_X,double dPose2_Y,double dPose2_Z,double dPose2_Rx,double dPose2_Ry,double dPose2_Rz, + double &dDistance,double &dAngle); + + +/** + * @brief: Linear interpolation calculation for pose + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dPose1_X~Rz : Pose of point 1 + * @param dPose2_X~Rz : Pose of point 2 + * @param dAlpha : Interpolation scale + * @param dPose3_X~Rz : Calculation result, pose of point 3 + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_PoseInterpolate(unsigned int boxID, unsigned int rbtID, double dPose1_X, double dPose1_Y, double dPose1_Z, double dPose1_Rx, double dPose1_Ry, double dPose1_Rz, + double dPose2_X,double dPose2_Y,double dPose2_Z,double dPose2_Rx,double dPose2_Ry,double dPose2_Rz,double dAlpha, + double &dPose3_X,double &dPose3_Y,double &dPose3_Z,double &dPose3_Rx,double &dPose3_Ry,double &dPose3_Rz); + +/*** + * @brief: Calculate the rotation center. P1,P2,P3 are the points before rotation and P4,P5,P6 are the points after rotation + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dPose1_X~Z : Pose of point 1 + * @param dPose2_X~Z : Pose of point 2 + * @param dPose3_X~Z : Pose of point 3 + * @param dPose4_X~Z : Pose of point 4 + * @param dPose5_X~Z : Pose of point 5 + * @param dPose6_X~Z : Pose of point 6 + * @param dUcs_X~Rz : Calculation result, UCS + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_PoseDefdFrame(unsigned int boxID, unsigned int rbtID, double dPose1_X, double dPose1_Y, double dPose1_Z, double dPose2_X, double dPose2_Y, double dPose2_Z, + double dPose3_X,double dPose3_Y,double dPose3_Z,double dPose4_X,double dPose4_Y,double dPose4_Z, + double dPose5_X,double dPose5_Y,double dPose5_Z,double dPose6_X,double dPose6_Y,double dPose6_Z, + double &dUcs_X,double &dUcs_Y,double &dUcs_Z,double &dUcs_Rx,double &dUcs_Ry,double &dUcs_Rz); + +/*** + * @brief: Calculate the two passing through intermediate points in arc using three-point teaching + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dPose1_X~Rz : Pose of start point + * @param dPose2_X~Rz : Pose of aux point + * @param dPose3_X~Rz : Pose of end point + * @param dRetPose1_X~Rz : Pose of point between start point and aux point + * @param dRetPose2_X~Rz : Pose of point between start point and aux point + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GetCircularViaPose(unsigned int boxID, unsigned int rbtID, + double dPose1_X, double dPose1_Y, double dPose1_Z, double dPose1_Rx, double dPose1_Ry, double dPose1_Rz, + double dPose2_X,double dPose2_Y,double dPose2_Z,double dPose2_Rx,double dPose2_Ry,double dPose2_Rz, + double dPose3_X,double dPose3_Y,double dPose3_Z,double dPose3_Rx,double dPose3_Ry,double dPose3_Rz, + double& dRetPose1_X,double& dRetPose1_Y,double& dRetPose1_Z,double& dRetPose1_Rx,double& dRetPose1_Ry,double& dRetPose1_Rz, + double& dRetPose2_X,double& dRetPose2_Y,double& dRetPose2_Z,double& dRetPose2_Rx,double& dRetPose2_Ry,double& dRetPose2_Rz); + +/*** + * @brief: Calculate UCS through 3-point plane + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dPose1_X~Z : Position of point 1 in Base and specified TCP + * @param dPose2_X~Z : Position of point 2 in Base and specified TCP + * @param dPose3_X~Z : Position of point 3 in Base and specified TCP + * @param dRetPose_X~Rz : Pose of UCS + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_CalUcsPlane(unsigned int boxID, unsigned int rbtID, + double dPose1_X, double dPose1_Y, double dPose1_Z, + double dPose2_X,double dPose2_Y,double dPose2_Z, + double dPose3_X,double dPose3_Y,double dPose3_Z, + double& dRetPose_X,double& dRetPose_Y,double& dRetPose_Z,double& dRetPose_Rx,double& dRetPose_Ry,double& dRetPose_Rz); + +/*** + * @brief: Calculate UCS through 2-point linee + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dPose1_X~Rz : Pose of point 1 in Base and specified TCP + * @param dPose2_X~Rz : Pose of point 2 in Base and specified TCP + * @param dRetPose_X~Rz : Pose of UCS + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_CalUcsLine(unsigned int boxID, unsigned int rbtID, + double dPose1_X, double dPose1_Y, double dPose1_Z, double dPose1_Rx, double dPose1_Ry, double dPose1_Rz, + double dPose2_X,double dPose2_Y,double dPose2_Z,double dPose2_Rx,double dPose2_Ry,double dPose2_Rz, + double& dRetPose_X,double& dRetPose_Y,double& dRetPose_Z,double& dRetPose_Rx,double& dRetPose_Ry,double& dRetPose_Rz); + +/*** + * @brief: Calculate TCP through 3-point + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dPose1_X~Rx : Pose of point 1 in Base and system default TCP + * @param dPose2_X~Rz : Pose of point 2 in Base and system default TCP + * @param dPose3_X~Rz : Pose of point 3 in Base and system default TCP + * @param dRetPose_X~Z : Position of TCP + * @param dRetPose_Rx~Rz : Orientation of TCP, usually 0 + * @param quality : quality of the result, 0 for good, 1 for poor, 2 for abnormal + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_CalTcp3P(unsigned int boxID, unsigned int rbtID, + double dPose1_X, double dPose1_Y, double dPose1_Z, double dPose1_Rx, double dPose1_Ry, double dPose1_Rz, + double dPose2_X,double dPose2_Y,double dPose2_Z,double dPose2_Rx,double dPose2_Ry,double dPose2_Rz, + double dPose3_X,double dPose3_Y,double dPose3_Z,double dPose3_Rx,double dPose3_Ry,double dPose3_Rz, + double& dRetPose_X,double& dRetPose_Y,double& dRetPose_Z,double& dRetPose_Rx,double& dRetPose_Ry,double& dRetPose_Rz, + int& quality); + + /*** + * @brief: Calculate TCP through 4-point + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dPose1_X~Rx : Pose of point 1 in Base and system default TCP + * @param dPose2_X~Rz : Pose of point 2 in Base and system default TCP + * @param dPose3_X~Rz : Pose of point 3 in Base and system default TCP + * @param dPose4_X~Rz : Pose of point 4 in Base and system default TCP + * @param dRetPose_X~Z : Position of TCP + * @param dRetPose_Rx~Rz : Orientation of TCP, usually 0 + * @param quality : quality of the result, 0 for good, 1 for poor, 2 for abnormal + * @param erroIndex_P1~4 : error index for the 4 source points, 0 for abnormal, 1 for normal + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_CalTcp4P(unsigned int boxID, unsigned int rbtID, + double dPose1_X, double dPose1_Y, double dPose1_Z, double dPose1_Rx, double dPose1_Ry, double dPose1_Rz, + double dPose2_X,double dPose2_Y,double dPose2_Z,double dPose2_Rx,double dPose2_Ry,double dPose2_Rz, + double dPose3_X,double dPose3_Y,double dPose3_Z,double dPose3_Rx,double dPose3_Ry,double dPose3_Rz, + double dPose4_X,double dPose4_Y,double dPose4_Z,double dPose4_Rx,double dPose4_Ry,double dPose4_Rz, + double& dRetPose_X,double& dRetPose_Y,double& dRetPose_Z,double& dRetPose_Rx,double& dRetPose_Ry,double& dRetPose_Rz, + int& quality, int& erroIndex_P1,int& erroIndex_P2,int& erroIndex_P3,int& erroIndex_P4); + +////////////////////////////////////////////////////////////////////////////////////// +//************************************************************************/ +//** Interfaces for TCP and UCS +//************************************************************************/ +////////////////////////////////////////////////////////////////////////////////////// +/** + * @brief:Set current TCP + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dTcp_X~Rz : Pose of TCP + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetTCP(unsigned int boxID, unsigned int rbtID, double dTcp_X, double dTcp_Y, double dTcp_Z, double dTcp_Rx, double dTcp_Ry, double dTcp_Rz); + +/** + * @brief: Set current UCS + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dUcs_X~Rz : Pose of UCS + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetUCS(unsigned int boxID, unsigned int rbtID, double dUcs_X, double dUcs_Y, double dUcs_Z, double dUcs_Rx, double dUcs_Ry, double dUcs_Rz); + +/** + * @brief: Read current TCP + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dTcp_X~Rz : Pose of TCP + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadCurTCP(unsigned int boxID, unsigned int rbtID, double& dTcp_X, double& dTcp_Y, double& dTcp_Z, double& dTcp_Rx, double& dTcp_Ry, double& dTcp_Rz); + +/** + * @brief: Read TCP list + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param TCPList : vector of TCP list + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadTCPList(unsigned int boxID, unsigned int rbtID, vector &TCPList); + +/** + * @brief: Read current UCS + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dUcs_X~Rz : Pose of UCS + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadCurUCS(unsigned int boxID, unsigned int rbtID, double& dUcs_X, double& dUcs_Y, double& dUcs_Z, double& dUcs_Rx, double& dUcs_Ry, double& dUcs_Rz); + +/** + * @brief: Read UCS list + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param TCPList : vector of UCS list + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadUCSList(unsigned int boxID, unsigned int rbtID, vector &UCSList); + +/** + * @brief: Set current TCP By Name + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sTcpName : TCP name + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetTCPByName(unsigned int boxID, unsigned int rbtID, string sTcpName); + +/** + * @brief: Set current UCS By Name + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sUcsName : UCS name + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetUCSByName(unsigned int boxID, unsigned int rbtID, string sUcsName); + +/** + * @brief: Read TCP By Name + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sName : TCP name + * @param dTcp_X~Rz : Pose of TCP + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadTCPByName(unsigned int boxID, unsigned int rbtID, + string sName, + double& dTcp_X, + double& dTcp_Y, + double& dTcp_Z, + double& dTcp_Rx, + double& dTcp_Ry, + double& dTcp_Rz); + +/** + * @brief: Read UCS By Name + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sName : UCS name + * @param dUcs_X~Rz: Pose of UCS + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadUCSByName(unsigned int boxID, unsigned int rbtID, + string sName, + double& dUcs_X, + double& dUcs_Y, + double& dUcs_Z, + double& dUcs_Rx, + double& dUcs_Ry, + double& dUcs_Rz); + +/** + * @brief: Configure TCP by Name + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sTcpName : TCP name + * @param dX~Rz : Pose of TCP + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ + //ROBOT_PRO_API int HRIF_ConfigTCP(unsigned int boxID, string sTcpName,double dX, double dY, double dZ, double dRx, double dRy, double dRz); + + + +////////////////////////////////////////////////////////////////////////////////////// +//************************************************************************/ +//** Interfaces for force control +//************************************************************************/ +////////////////////////////////////////////////////////////////////////////////////// +ROBOT_PRO_API int HRIF_GetForceParams(unsigned int boxID, unsigned int rbtID, double* dInertia, double* dDamping, double* dStiffness); + +/** + * @brief: Set force parameters + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dInertia : Mass (size=6) + * @param dDamping : Damp (size=6) + * @param dStiffness : Stiffness (size=6) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetForceParams(unsigned int boxID, unsigned int rbtID, double* dInertia, double* dDamping, double* dStiffness); + +/** + * @brief: Set force control parameters + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dForceLimit : Force limit (size=6) + * @param dDistLimit : Distance limit (size=6) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetForceControlParams(unsigned int boxID, unsigned int rbtID, double* dForceLimit, double* dDistLimit); + +/** + * @brief: Set force control switch + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dEnbaleFlag : true for open, false for closed + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetForceMode(unsigned int boxID, unsigned int rbtID, bool dEnbaleFlag); + +/** + * @brief: Set force control status + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nState : 0 for closed, 1 for open + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetForceControlState(unsigned int boxID, unsigned int rbtID, int nState); +/** + * @brief: Read force control status + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nState : 0 for closed, 1 for seeking, 2 for seeking completed, 3 for free drive + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadForceControlState(unsigned int boxID, unsigned int rbtID, int& nState); +/** + * @brief: Set force sensor direction align with TCP + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nState : 0 for not align with TCP, 1 for align with TCP + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetForceToolCoordinateMotion(unsigned int boxID, unsigned int rbtID, int nMode); +/** + * @brief: Disable force control + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ForceControlInterrupt(unsigned int boxID, unsigned int rbtID); + +/** + * @brief: Enable force control + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ForceControlContinue(unsigned int boxID, unsigned int rbtID); +/** + * @brief: Reset force sensor to zero + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetForceZero(unsigned int boxID, unsigned int rbtID); +/** + * @brief: Set max velocity of force seeking + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dMaxLinearVelocity : Max linear velocity + * @param dMaxAngularVelocity : Max angular velocity + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetMaxSearchVelocities(unsigned int boxID, unsigned int rbtID, double dMaxLinearVelocity, double dMaxAngularVelocity); + +/** + * @brief: Set control freedom + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nX~Rz : 0 for closed / 1 for open + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetControlFreedom(unsigned int boxID, unsigned int rbtID, int nX, int nY, int nZ, int nRx, int nRy, int nRz); + +/** + * @brief: Set force control strategy + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nState : 0 for Compliant force and 1 for Constant force + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetForceControlStrategy(unsigned int boxID, unsigned int rbtID, int nState); +/** + * @brief: Set force sensor pose + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz : Pose + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetFreeDrivePositionAndOrientation(unsigned int boxID, unsigned int rbtID, double dX, double dY, double dZ, double dRx, double dRy, double dRz); +/** + * @brief: Set PID for force seeking + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dFp~Td : PID + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetPIDControlParams(unsigned int boxID, unsigned int rbtID, double dFp, double dFi, double dFd, double dTp, double dTi, double dTd); +/** + * @brief: Set mass parameters + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz : Mass parameters + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetMassParams(unsigned int boxID, unsigned int rbtID, double dX, double dY, double dZ, double dRx, double dRy, double dRz); +/** + * @brief: Set damp parameters + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz : Damp parameters + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetDampParams(unsigned int boxID, unsigned int rbtID, double dX, double dY, double dZ, double dRx, double dRy, double dRz); +/** + * @brief: Set stiffness parameters + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz : Stiffness parameters + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetStiffParams(unsigned int boxID, unsigned int rbtID, double dX, double dY, double dZ, double dRx, double dRy, double dRz); +/** + * @brief: Set force control goal + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz: Goal values + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetForceControlGoal(unsigned int boxID, unsigned int rbtID, double dX, double dY, double dZ, double dRX, double dRY, double dRZ); +/** + * @brief: Set control seeking goal + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dWrench_X~Rz : Seeking force + * @param dDistance_X~Rz : Seeking distance + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetControlGoal(unsigned int boxID, unsigned int rbtID, double dWrench_X, double dWrench_Y, double dWrench_Z, double dWrench_Rx, double dWrench_Ry, double dWrench_Rz, + double dDistance_X,double dDistance_Y, double dDistance_Z,double dDistance_Rx,double dDistance_Ry, double dDistance_Rz); + +/** + * @brief: Set force data limit + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dMax_X~Rz) : Max limit + * @param dMin_X~Rz) : Min limit + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetForceDataLimit(unsigned int boxID, unsigned int rbtID, double dMax_X, double dMax_Y, double dMax_Z, double dMax_Rx, double dMax_Ry, double dMax_Rz, + double dMin_X,double dMin_Y, double dMin_Z,double dMin_Rx,double dMin_Ry, double dMin_Rz); +/** + * @brief: Set force distance limit + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dAllowDistance : Allowed seeking distance + * @param dStrengthLevel : Power term for the deviation from boundary. 2 for square, 3 for cubic + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetForceDistanceLimit(unsigned int boxID, unsigned int rbtID, double dAllowDistance, double dStrengthLevel); + +/** + * @brief: Set force free drive mode + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nEnableFlag : 0 for closed, 1 for open + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetForceFreeDriveMode(unsigned int boxID, unsigned int rbtID, int nEnableFlag); + +/** + * @brief:Read force control calibration data + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz : Calibration data + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadFTCabData(unsigned int boxID, unsigned int rbtID, double& dX, double& dY, double& dZ, double& dRX, double& dRY, double& dRZ); + +/** + * @brief: Set the end freedom in free drive + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nX-nRz : The available freedom parameters in free drive + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetFreeDriveMotionFreedom(unsigned int boxID, unsigned int rbtID, int nX, int nY, int nZ, int nRx, int nRy, int nRz); + +/** + * @brief: Set force free drive translation compliance and rotation compliance + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param Linear : translation compliance + * @param Angular : rotation compliance + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetFTFreeFactor(unsigned int boxID, unsigned int rbtID, double Linear, double Angular); + +/** + * @brief: Set speed mode for force free drive + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nMode : speed mode, 0 as normal mode, 1 as slow mode, 2 as fast mode,3 as Welding mode + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetFTFreeDriveSpeedMode(unsigned int boxID, unsigned int rbtID, int nMode); + +/** + * @brief:Read force control calibration data + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nMode : speed mode, 0 as normal mode, 1 as slow mode, 2 as fast mode,3 as Welding mode + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadFTFreeDriveSpeedMode(unsigned int boxID, unsigned int rbtID, int &nMode); + +/** + * @brief: Set the force control free drive filter mode + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nMode : filter mode, 0 as close filter, 1 as open input low-pass filter, 2 as open output mean filter + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetFTFreeDriveFilter(unsigned int boxID, unsigned int rbtID, int nMode); + +/** + * @brief:Read the force control free drive filter mode + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nMode : filter mode, 0 as close filter, 1 as open input low-pass filter, 2 as open output mean filter + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadFTFreeDriveFilter(unsigned int boxID, unsigned int rbtID, int &nMode); + +/** + * @brief: Set the maximum value, minimum value of the tangential force in the x/y direction and the maximum speed of lifting + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dMax : Max tangential force (N) + * @param dMin : Min tangential force (N) + * @param dVel : Max lifting Velocity (mm/s) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetTangentForceBounds(unsigned int boxID, unsigned int rbtID, double dMax, double dMin, double dVel); + +/** + * @brief: Set the orientation compensation force size and vector direction [x,y,z] in FreeDrive mode + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dForce : Compensation force size(N) + * @param dX-dZ :The vactor direction of compensation force based Base + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetFreeDriveCompensateForce(unsigned int boxID, unsigned int rbtID, double dForce, double dX, double dY, double dZ); + +/** + * @brief: Set the six-dimensional force activation threshold (force and torque) + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dForceThreshold : Force activation threshold(N) + * @param dTorqueThreshold : Torque activation threshold (Nm) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetFTWrenchThresholds(unsigned int boxID, unsigned int rbtID, double dForceThreshold, double dTorqueThreshold); + +/** + * @brief: Set the maximum linear speed and attitude angular speed of force free drive + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dMaxLinearVelocity : Max linear speed of force free drive(mm/s) + * @param dMaxAngularVelocity : Max attitude angular speed of force free drive (°/s) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetMaxFreeDriveVel(unsigned int boxID, unsigned int rbtID, double dMaxLinearVelocity, double dMaxAngularVelocity); + +/** + * @brief: Read the end degrees of freedom of the force free drive + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nX-nRz : The available freedom parameters in free drive + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadFTMotionFreedom(unsigned int boxID,unsigned int rbtID,int& nX,int& nY,int& nZ,int& nRx,int& nRy,int& nRz); + +/** + * @brief: Set each degree of freedom (X/Y/Z/RX/RY/RZ) to force control the maximum distance of exploration + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param Dis_X-Dis_RZ : Max distance of exploration(mm) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetMaxSearchDistance(unsigned int boxID,unsigned int rbtID,double Dis_X,double Dis_Y,double Dis_Z,double Dis_RX,double Dis_RY,double Dis_RZ); + +/** + * @brief: Set constant force control stability phase boundary + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param Pos_X-Pos_RZ : Max distance of positive (mm) + * @param Neg_X-Neg_RZ : Max distance of negative (mm) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetSteadyContactDeviationRange(unsigned int boxID,unsigned int rbtID,double Pos_X,double Pos_Y,double Pos_Z,double Pos_RX,double Pos_RY,double Pos_RZ, + double Neg_X,double Neg_Y,double Neg_Z,double Neg_RX,double Neg_RY,double Neg_RZ); + +/** + * @brief: Set current UCS + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dUcs_X~Rz : Pose of UCS + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetFTUCS(unsigned int boxID, unsigned int rbtID, double dUcs_X, double dUcs_Y, double dUcs_Z, double dUcs_Rx, double dUcs_Ry, double dUcs_Rz); + +/** + * @brief: Read current UCS + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dUcs_X~Rz : Pose of UCS + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadFTUCS(unsigned int boxID, unsigned int rbtID, double& dUcs_X, double& dUcs_Y, double& dUcs_Z, double& dUcs_Rx, double& dUcs_Ry, double& dUcs_Rz); + + +//************************************************************************/ +//** Interfaces for moving +//************************************************************************/ +/** + * @brief: Start joint short jog + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nAxisId : Axis ID, 0~5 + * @param nDirection : Moving direction, 0 for negative and 1 for positive + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ShortJogJ(unsigned int boxID, unsigned int rbtID, int nAxisId, int nDirection); + +/** + * @brief: Cartesian short jog + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nAxisId : Axis ID, 0~5 + * @param nDirection : Moving direction, 0 for negative and 1 for positive + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ShortJogL(unsigned int boxID, unsigned int rbtID, int nAxisId, int nDirection); + +/** + * @brief: Start joint long jog + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nAxisId : Axis ID, 0~5 + * @param nDirection : Moving direction, 0 for negative and 1 for positive + * @param nState : 0 for closed and 1 for open + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_LongJogJ(unsigned int boxID, unsigned int rbtID, int nAxisId, int nDirection, int nState); + +/** + * @brief: Start cartesian long jog + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nAxisId : Axis ID, 0~5 + * @param nDirection : Moving direction, 0 for negative and 1 for positive + * @param nState : 0 for closed and 1 for open + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_LongJogL(unsigned int boxID, unsigned int rbtID, int nAxisId, int nDirection, int nState); + +/** + * @brief: Continue long jog. Successively call this function after HRIF_ShortJogL or HRIF_LongJogJ with less than 500 ms interval to keep continuous moving. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_LongMoveEvent(unsigned int boxID, unsigned int rbtID); + +////////////////////////////////////////////////////////////////////////////////////// + +/** + * @brief: Check if the motion is done + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param bDone : true for done, false for not done + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call, the value of bDone is meaningless + */ +ROBOT_PRO_API int HRIF_IsMotionDone(unsigned int boxID, unsigned int rbtID, bool& bDone); + +/** + * @brief: Check if the waypoint blending is done + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param bDone : true for done, false for not done + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call, the value of bDone is meaningless + */ +ROBOT_PRO_API int HRIF_IsBlendingDone(unsigned int boxID, unsigned int rbtID, bool& bDone); + +/** + * @brief: Start waypoint move. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nMoveType : Move type, 0 for MoveJ, 1 for MoveL + * @param dX~Rz : Target pose, invalid when IsUseJoint equals 1. Target joint positions is obtained through inversely solving from this pose when IsUseJoint equals 0 + * @param dJ1~6 : Target joint positions when IsUseJoint equals 1; Reference joint positions for the inversely solving when IsUseJoint equals 0. + * @param dTcp_X~Rz : Target TCP, invalid when IsUseJoint equals 1 + * @param dUcs_X~Rz : Target UCS, invalid when IsUseJoint equals 1 + * @param dVelocity : Motion speed, ( mm/s or °/s) + * @param dAcc : Motion acceleration, ( mm/(s^2) or °/(s^2) ) + * @param dRadius : Blending radius, (mm) + * @param nIsUseJoint : Using joint or not, usable when MoveType equals 0, 0 for not using joint, 1 for using joint + * @param nIsSeek,nIOBit,nIOState:Seeking or not, 1 for seeking; Motion stops when DO-nIOBit equals nIoState + * @param strCmdID:Command ID, waypoint ID also, customized or set to 1, 2, 3 in order + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_WayPointEx(unsigned int boxID, unsigned int rbtID, int nMoveType, + double dX, double dY, double dZ, double dRx, double dRy, double dRz, + double dJ1, double dJ2, double dJ3, double dJ4, double dJ5, double dJ6, + double dTcp_X, double dTcp_Y, double dTcp_Z, double dTcp_Rx, double dTcp_Ry, double dTcp_Rz, + double dUcs_X, double dUcs_Y, double dUcs_Z, double dUcs_Rx, double dUcs_Ry, double dUcs_Rz, + double dVelocity, double dAcc, double dRadius, int nIsUseJoint, int nIsSeek, int nIOBit, int nIOState, string strCmdID); +/** + * @brief: Start waypoint move. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nMoveType : Move type, 0 for MoveJ, 1 for MoveL + * @param dX~Rz : Target pose, invalid when IsUseJoint equals 1. Target joint positions is obtained through inversely solving from this pose when IsUseJoint equals 0 + * @param dJ1~6 : Target joint positions when IsUseJoint equals 1; Reference joint positions for the inversely solving when IsUseJoint equals 0. + * @param sTcpName : Target TCP name, invalid when IsUseJoint equals 1, "TCP" is valid as default + * @param sUcsName : Target UCS name, invalid when IsUseJoint equals 1, "Base" is valid as default + * @param dVelocity : Motion speed, mm/s or °/s + * @param dAcc : Motion acceleration, mm/(s^2) or °/(s^2) + * @param dRadius : Blending radius, mm + * @param nIsUseJoint : Using joint or not, usable when MoveType equals 0, 0 for not using joint, 1 for using joint + * @param nIsSeek,nIOBit,nIOState:Seeking or not, 1 for seeking; Motion stops when DO-nIOBit equals nIoState + * @param strCmdID:Command ID, waypoint ID also, customized or set to 1, 2, 3 in order + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_WayPoint(unsigned int boxID, unsigned int rbtID, int nMoveType, + double dX, double dY, double dZ, double dRx, double dRy, double dRz, + double dJ1, double dJ2, double dJ3, double dJ4, double dJ5, double dJ6, + string sTcpName, string sUcsName, double dVelocity, double dAcc, double dRadius, + int nIsUseJoint,int nIsSeek, int nIOBit, int nIOState, string strCmdID); + +/** + * @brief: Start waypoint move. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nMoveType : Move type, 0 for MoveJ, 1 for MoveL, 2 for MoveC + * @param dEndPos_X~Rz : Target pose, invalid when IsUseJoint equals 1. Target pose is obtained through inverse kinematics from this pose when IsUseJoint equals 0 +* @param dAuxPos_X~Rz : Target auxiliary pose invalid when IsUseJoint equals 1, used as the middle point when nMoveType equals 2. Target pose is obtained through inverse kinematics from this pose when IsUseJoint equals 0 + * @param dJ1~6 : Target joint positions when IsUseJoint equals 1; Reference joint positions for the inverse kinematics when IsUseJoint equals 0. + * @param sTcpName : Target TCP name, invalid when IsUseJoint equals 1, "TCP" is valid as default + * @param sUcsName : Target UCS name, invalid when IsUseJoint equals 1, "Base" is valid as default + * @param dVelocity : Motion speed, mm/s or °/s + * @param dAcc : Motion acceleration, mm/(s^2) or °/(s^2) + * @param dRadius : Blending radius, mm + * @param nIsUseJoint : Using joint or not, usable when MoveType equals 0, 0 for not using joint, 1 for using joint + * @param nIsSeek,nIOBit,nIOState:Seeking or not, 1 for seeking; Motion stops when DO-nIOBit equals nIoState + * @param strCmdID:Command ID, waypoint ID also, customized or set to 1, 2, 3 in order + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_WayPoint2(unsigned int boxID, unsigned int rbtID, int nMoveType, + double dEndPos_X, double dEndPos_Y, double dEndPos_Z, double dEndPos_Rx, double dEndPos_Ry, double dEndPos_Rz, + double dAuxPos_X, double dAuxPos_Y, double dAuxPos_Z, double dAuxPos_Rx, double dAuxPos_Ry, double dAuxPos_Rz, + double dJ1, double dJ2, double dJ3, double dJ4, double dJ5, double dJ6, + string sTcpName, string sUcsName, double dVelocity, double dAcc, double dRadius, + int nIsUseJoint,int nIsSeek, int nIOBit, int nIOState, string strCmdID); + + +/** + * @brief: Start joint move. HRIF_WayPoint is more recommended yet + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz : Target pose, invalid when IsUseJoint equals 1. Target joint positions is obtained through inversely solving from this pose when IsUseJoint equals 0 + * @param dJ1~6 : Target joint positions when IsUseJoint equals 1; Reference joint positions for the inversely solving when IsUseJoint equals 0. + * @param sTcpName : Target TCP name, invalid when IsUseJoint equals 1, "TCP" is valid as default + * @param sUcsName : Target UCS name, invalid when IsUseJoint equals 1, "Base" is valid as default + * @param dVelocity : Motion speed, mm/s or °/s + * @param dAcc : Motion acceleration, mm/(s^2) or °/(s^2) + * @param dRadius : Blending radius, mm + * @param nIsUseJoint : Using joint or not, usable when MoveType equals 0, 0 for not using joint, 1 for using joint + * @param nIsSeek,nIOBit,nIOState:Seeking or not, 1 for seeking; Motion stops when DO-nIOBit equals nIoState + * @param strCmdID:Command ID, waypoint ID also, customized or set to 1, 2, 3 in order + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveJ(unsigned int boxID, unsigned int rbtID, double dX, double dY, double dZ, double dRx, double dRy, double dRz, + double dJ1, double dJ2, double dJ3, double dJ4, double dJ5, double dJ6, + string sTcpName, string sUcsName, double dVelocity, double dAcc, double dRadius, + int nIsUseJoint,int nIsSeek, int nIOBit, int nIOState, string strCmdID); + +/** + * @brief: Start linear move. HRIF_WayPoint is more recommended yet + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz : Target pose + * @param dJ1~6 : Reference joint positions near to the target pose + * @param sTcpName : TCP name + * @param sUcsName : UCS name + * @param dVelocity : Motion speed, mm/s or °/s + * @param dAcc : Motion acceleration, mm/(s^2) or °/(s^2) + * @param dRadius :Blending radius, mm + * @param nIsSeek,nIOBit,nIOState:Seeking or not, 1 for seeking; Motion stops when DO-nIOBit equals nIoState + * @param strCmdID:Command ID, waypoint ID also, customized or set to 1, 2, 3 in order + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveL(unsigned int boxID, unsigned int rbtID, double dX, double dY, double dZ, double dRx, double dRy, double dRz, + double dJ1, double dJ2, double dJ3, double dJ4, double dJ5, double dJ6, + string sTcpName, string sUcsName, double dVelocity, double dAcc, double dRadius, + int nIsSeek, int nIOBit, int nIOState, string strCmdID); + +/** + * @brief: Start round move + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dStartPos_X~Rz : Start pose + * @param dAuxPos_X~Rz : Via pose + * @param dEndPos_X~Rz : End pose + * @param nFixedPosure : 0 for unfixed orientation, 1 for fixed orientation, 2 for newly added gradual orientation arc motion + * + * @param nMoveCType : 1 for arc, 0 for circle + * @param dRadLen : Invalid when nMoveCType equals 1; Circles when nMoveCType equals 0 + * + * @param dVelocity : Motion speed, mm/s + * @param dAcc : Motion acceleration, mm/(s^2) + * @param dRadius : Blending radius, mm + * @param sTcpName : TCP name + * @param sUcsName : UCS name + * @param sCmdID : Command ID + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveC(unsigned int boxID, unsigned int rbtID, + double dStartPos_X,double dStartPos_Y,double dStartPos_Z,double dStartPos_Rx,double dStartPos_Ry,double dStartPos_Rz, + double dAuxPos_X,double dAuxPos_Y,double dAuxPos_Z,double dAuxPos_Rx,double dAuxPos_Ry,double dAuxPos_Rz, + double dEndPos_X,double dEndPos_Y,double dEndPos_Z,double dEndPos_Rx,double dEndPos_Ry,double dEndPos_Rz, + int nFixedPosure,int nMoveCType,double dRadLen,double dVelocity, double dAcc, + double dRadius, string sTcpName, string sUcsName, string sCmdID); + +/** + * @brief: Start round move + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dStartPos_X~Rz : Start pose + * @param dAux1Pos_X~Rz: Via pose 1 + * @param dAux2Pos_X~Rz : Via pose 2 + * @param dAngle : The angle at which the entire circle runs + * @param sTcpName : TCP name + * @param sUcsName : UCS name + * @param dVelocity : Motion speed, mm/s + * @param dAcc : Motion acceleration, mm/(s^2) + * @param dRadius : Blending radius, mm + * @param nIsSeek,nIOBit,nIOState:Seeking or not, 1 for seeking; Motion stops when DO-nIOBit equals nIoState + * @param sCmdID : Command ID + * @param vecLastPoint : The end pose of the trajectory + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveCAngle(unsigned int boxID, unsigned int rbtID, + double dStartPos_X,double dStartPos_Y,double dStartPos_Z,double dStartPos_Rx,double dStartPos_Ry,double dStartPos_Rz, + double dAux1Pos_X,double dAux1Pos_Y,double dAux1Pos_Z,double dAux1Pos_Rx,double dAux1Pos_Ry,double dAux1Pos_Rz, + double dAux2Pos_X,double dAux2Pos_Y,double dAux2Pos_Z,double dAux2Pos_Rx,double dAux2Pos_Ry,double dAux2Pos_Rz, + double dAngle, string sTcpName, string sUcsName, double dVelocity, double dAcc, + double dRadius, int nIsSeek, int nBit, int nDIType, string sCmdID, vector &vecLastPoint); + +/** + * @brief: Start round move + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dAuxPos_X~Rz : Via pose + * @param dEndPos_X~Rz : End pose + * @param nFixedPosure : 0 for Not fixed posture and posture angle change less than 180 °, 1 for fixed orientation, 2 for Unsteady posture and posture angle change greater than 180 ° + * + * @param dRadLen : When the arc length is 0, it means that the target pose is determined by endPose. If set to 360 °, it can run a full circle, and if set to 720 °, it can run two circles + * + * @param dVelocity : Motion speed, mm/s + * @param dAcc : Motion acceleration, mm/(s^2) + * @param dRadius : Blending radius, mm + * @param sTcpName : TCP name + * @param sUcsName : UCS name + * @param sCmdID : Command ID + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveC2(unsigned int boxID, unsigned int rbtID, + double dAuxPos_X,double dAuxPos_Y,double dAuxPos_Z,double dAuxPos_Rx,double dAuxPos_Ry,double dAuxPos_Rz, + double dEndPos_X,double dEndPos_Y,double dEndPos_Z,double dEndPos_Rx,double dEndPos_Ry,double dEndPos_Rz, + int nFixedPosure,double dRadLen,double dVelocity, double dAcc, + double dRadius, string sTcpName, string sUcsName, string sCmdID); + +/** + * @brief: Start zigzag move + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dStartPos_X~Rz : Start pose + * @param dEndPos_X~Rz : End pose + * @param dPlanePos_X~Rz : Plane pose + * @param dVelocity : Motion speed, mm/s + * @param dAcc : Motion acceleration, mm/(s^2) + * @param dWIdth : Width + * @param dDensity : Density, mm + * @param nEnableDensity : 0 is disable density and 1 for enable density + * @param nEnablePlane : Use plane pose or not; UCS is automatically used if plane pose is not used + * @param nEnableWaiTime : 0 for disable waiting time, 1 for enable waiting time + * @param nPosiTime : Waiting time (ms) for Positive turning point + * @param nNegaTime : Waiting time (ms) for Negative turning point + * @param dRadius : Blending radius, mm + * @param sTcpName : TCP name + * @param sUcsName : UCS name + * @param sCmdID : Command ID + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveZ(unsigned int boxID, unsigned int rbtID, + double dStartPos_X,double dStartPos_Y,double dStartPos_Z,double dStartPos_Rx,double dStartPos_Ry,double dStartPos_Rz, + double dEndPos_X,double dEndPos_Y,double dEndPos_Z,double dEndPos_Rx,double dEndPos_Ry,double dEndPos_Rz, + double dPlanePos_X,double dPlanePos_Y,double dPlanePos_Z,double dPlanePos_Rx,double dPlanePos_Ry,double dPlanePos_Rz, + double dVelocity, double dAcc, double dWIdth, double dDensity, int nEnableDensity, int nEnablePlane, int nEnableWaiTime, + int nPosiTime, int nNegaTime, double dRadius, string sTcpName, string sUcsName, string sCmdID); + +/** + * @brief: Start linear weave move + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dStartPos_X~Rz : Start pose + * @param dEndPos_X~Rz : End pose + * @param dVelocity : Motion speed, mm/s + * @param dAcc : Motion acceleration, mm/(s^2) + * @param dRadius : Blending radius, mm + * @param dAmplitude : Weaving amplitude, mm + * @param dIntervalDistance : Referenced weaving interval, robot will rectify it slightly to ensure the StartPos and EndPos be passed through after some weaving cycles, mm + * @param nWeaveFrameType : the way to determine the weaving plane and weaving direction + * 0: The +X of weaving plane is from StartPos to EndPos. The +Z(normal direction) of weaving plane is along TCP +Z. The +Y of weaving plane is determined by +X and +Z. The start direction of weaving move is toward the +Y side. + * 1: The +X of weaving plane is from StartPos to EndPos. The Z of weaving plane is parallel with TCP Z. The +Y of weaving plane is along TCP +Y. The start direction of weaving move is toward the +Y side. + * + * + * + * @param dElevation : The elevation of the weave, degree + * @param dAzimuth : The azimuth of the weave, degree + * @param dCentreRise : The bulge height of the welding torch at the weaving weld center, mm + * @param nEnableWaiTime : 0 to disable waiting time, 1 to enable waiting time + * @param nPosiTime : Waiting time (ms) at Positive turning point + * @param nNegaTime : Waiting time (ms) at Negative turning point + * @param sTcpName : TCP name + * @param sUcsName : UCS name + * @param sCmdID : Command ID + * @param vecLastPoint : The end pose of the trajectory + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveLinearWeave(unsigned int boxID, unsigned int rbtID, + double dStartPos_X,double dStartPos_Y,double dStartPos_Z,double dStartPos_Rx,double dStartPos_Ry,double dStartPos_Rz, + double dEndPos_X,double dEndPos_Y,double dEndPos_Z,double dEndPos_Rx,double dEndPos_Ry,double dEndPos_Rz, + double dVelocity, double dAcc, double dRadius, double dAmplitude, double dIntervalDistance, int nWeaveFrameType, double dElevation, + double dAzimuth, double dCentreRise, int nEnableWaiTime, int nPosiTime, int nNegaTime, string sTcpName, string sUcsName, string sCmdID, vector &vecLastPoint); + +/** + * @brief: Start circular weave move + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dStartPos_X~Rz : Start pose + * @param dAuxPos_X~Rz : via pose + * @param dEndPos_X~Rz : End pose + * @param dVelocity : Motion speed, mm/s + * @param dAcc : Motion acceleration, mm/(s^2) + * @param dRadius : Blending radius, mm + * @param nOrientMode : 0 for unfixed orientation, 1 for fixed orientation + * @param nMoveWhole : 0 for circle, 1 for arc + * @param dMoveWholeLen : Invalid when nMoveCType equals 1; Circles when nMoveCType equals 0 + * + * @param dAmplitude : Amplitude, mm + * @param dIntervalDistance : Referenced weaving interval, robot will rectify it slightly to ensure the StartPos and EndPos be passed through after some weaving cycles, mm + * @param nWeaveFrameType : the way to determine the weaving plane and weaving direction + * 0: The +X of weaving plane is from StartPos to EndPos. The +Z(normal direction) of weaving plane is along TCP +Z. The +Y of weaving plane is determined by +X and +Z. The start direction of weaving move is toward the +Y side. + * 1: The +X of weaving plane is from StartPos to EndPos. The Z of weaving plane is parallel with TCP Z. The +Y of weaving plane is along TCP +Y. The start direction of weaving move is toward the +Y side. + * + * + * + * @param dElevation : The elevation of the weave, degree + * @param dAzimuth : The azimuth of the weave, degree + * @param dCentreRise : The bulge height of the welding torch at the weaving weld center, mm + * @param nEnableWaiTime : 0 to disable waiting time, 1 to enable waiting time + * @param nPosiTime : Waiting time (ms) at Positive turning point + * @param nNegaTime : Waiting time (ms) at Negative turning point + * @param sTcpName : TCP name + * @param sUcsName : UCS name + * @param sCmdID : Command ID + * @param vecLastPoint : The end pose of the trajectory + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveCircularWeave(unsigned int boxID, unsigned int rbtID, + double dStartPos_X,double dStartPos_Y,double dStartPos_Z,double dStartPos_Rx,double dStartPos_Ry,double dStartPos_Rz, + double dAuxPos_X,double dAuxPos_Y,double dAuxPos_Z,double dAuxPos_RX,double dAuxPos_RY,double dAuxPos_RZ, + double dEndPos_X,double dEndPos_Y,double dEndPos_Z,double dEndPos_Rx,double dEndPos_Ry,double dEndPos_Rz, + double dVelocity, double dAcc, double dRadius, int nOrientMode, int nMoveWhole, double dMoveWholeLen, + double dAmplitude, double dIntervalDistance, int nWeaveFrameType, double dElevation, double dAzimuth, + double dCentreRise, int nEnableWaiTime, int nPosiTime, int nNegaTime, string sTcpName, string sUcsName, string sCmdID, vector &vecLastPoint); + +/** + * @brief: Start elliptical move + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dP1_X~Rz : Teaching p1 (p1~p5 should be listed as sequence in arc) + * @param dP2_X~Rz : Teaching p2 + * @param dP3_X~Rz : Teaching p3 + * @param dP4_X~Rz : Teaching p4 + * @param dP5_X~Rz : Teaching p5 + * @param nOrientMode : 0 is for arc, 1 is for circle + * @param nMoveType : 0 for unfixed orientation , 1 for fixed orientation + * @param dArcLength : Arc length (mm) + * @param dVelocity : Motion speed, mm/s + * @param dAcc : Motion acceleration, mm/(s^2) + * @param dRadius : Blending radius, mm + * @param sTcpName : TCP name + * @param sUcsName : UCS name + * @param sCmdID : Command ID + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ + +ROBOT_PRO_API int HRIF_MoveE(unsigned int boxID, unsigned int rbtID, + double dP1_X, double dP1_Y, double dP1_Z, double dP1_Rx, double dP1_Ry, double dP1_Rz, + double dP2_X, double dP2_Y, double dP2_Z, double dP2_Rx, double dP2_Ry, double dP2_Rz, + double dP3_X, double dP3_Y, double dP3_Z, double dP3_Rx, double dP3_Ry, double dP3_Rz, + double dP4_X, double dP4_Y, double dP4_Z, double dP4_Rx, double dP4_Ry, double dP4_Rz, + double dP5_X, double dP5_Y, double dP5_Z, double dP5_Rx, double dP5_Ry, double dP5_Rz, + int nOrientMode, int nMoveType, double dArcLength, + double dVelocity, double dAcc, double dRadius, string sTcpName, string sUcsName, string sCmdID); + +/** + * @brief: Start spiral move + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dSpiralIncrement : Spiral Increment radius + * @param dSpiralRadius : Spiral end radius + * @param dVelocity : Motion speed, mm/s + * @param dAcc : Motion acceleration, mm/(s^2) + * @param dRadius : Blending radius, mm + * @param sUcsName : UCS name + * @param sCmdID : Command ID + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveS(unsigned int boxID, unsigned int rbtID, double dSpiralIncrement, double dSpiralRadius, + double dVelocity, double dAcc, double dRadius, string sTCPName, string sUcsName, string sCmdID); + +/** + * @brief: Start relative joint move + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nAxisId: Axis ID , 0~5 for jonit 1 ~6 + * @param nDirection: Move direction, 0 for negative,1 for positive + * @param dDistance: Move distance (°) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveRelJ(unsigned int boxID, unsigned int rbtID, int nAxisId, int nDirection, double dDistance); + +/** + * @brief: Start relative linear move + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nPoseId: Pose ID, 0~5 for X, Y, Z, Rx, Ry, Rz + * @param nDirection: Move direction, 0 for negative, 1 for positive + * @param dDistance: Move distance (mm or °) + * @param nToolMotion : Tool motion, 0 for UCS based move, 1 for TCP based move + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveRelL(unsigned int boxID, unsigned int rbtID, int nPoseId, int nDirection, double dDistance, int nToolMotion); + +/** + * @brief: Start relative wayPoint + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nType : Move type, 0 for joint, 1 for linear + * @param nPointList : Using point saved in points list or not, 0 for not using, 1 for using + * @param dPos_X~Rz : Target pose + * @param dPos_J1~6 : Target joint positions + * @param nrelMoveType : Relative move type, 0 for absolute value, 1 for superimposed value + * @param nAxisMask_1~6 : Is Axis/Direction Moving, 0 for not moving, 1 for moving + * @param dTarget_1~6 : Moving distance; invalid when nAxisMask equals 0; joint move an absolute or superimposed distance when nAxisMask equals 1 and nType equals 0; Pose move an absolute or superimposed distance when nAxisMask equals 1 and nType equals 1. + * @param sTcpName : Target TCP name, invalid when IsUseJoint equals 1, "TCP" is valid as default + * @param sUcsName : Target UCS name, invalid when IsUseJoint equals 1, "Base" is valid as default + * @param dVelocity: Velocity (mm/s) or (°/s) + * @param dAcc: Acceleration (mm/s^2) or (°/s^2) + * @param dRadius: Blending radius, mm + * @param nIsUseJoint : Using joint or not, usable when MoveType equals 0, 0 for not using joint, 1 for using joint + * @param nIsSeek,nIOBit,nIOState:Seeking or not, 1 for seeking; Motion stops when DO-nIOBit equals nIoState + * @param strCmdID:Command ID, waypoint ID also, customized or set to 1, 2, 3 in order + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_WayPointRel(unsigned int boxID, unsigned int rbtID, + int nType, + int nPointList, + double dPos_X, double dPos_Y, double dPos_Z, double dPos_Rx, double dPos_Ry, double dPos_Rz, + double dPos_J1, double dPos_J2, double dPos_J3, double dPos_J4, double dPos_J5, double dPos_J6, + int nrelMoveType, + int nAxisMask_1, int nAxisMask_2, int nAxisMask_3, int nAxisMask_4, int nAxisMask_5, int nAxisMask_6, + double dTarget_1, double dTarget_2, double dTarget_3, double dTarget_4, double dTarget_5, double dTarget_6, + string sTcpName, string sUcsName, + double dVelocity, double dAcc, double dRadius, + int nIsUseJoint, int nIsSeek, int nIOBit, int nIOState, string strCmdID); + +//************************************************************************/ +//** Interfaces for trajectory +//************************************************************************/ +/** + * @brief: Initialize path + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nRawDataType : Raw points data type, 0 for ACS, 1 for PCS + * @param sPathName : Path name + * @param dSpeedRatio : Speed override for PathJ calculation + * @param dRadius : Blending radius for PathJ calculation, mm + * @param dVelocity : Velocity for PathL calculation + * @param dAcc : Acceleration for PathL calculation + * @param dJerk : Jerk for PathL calculation + * @param sUcsName : UCS name of raw point for PathL calculation + * @param sTcpName : TCP name of raw point for PathL calculation + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_InitPath(unsigned int boxID, unsigned int rbtID, int nRawDataType, string sPathName, double dSpeedRatio, + double dRadius, double dVelocity, double dAcc, double dJerk, string sUcsName, string sTcpName); + +/** + * @brief: Push raw points to path + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : Path name + * @param sPoints : Points data, separated by comma, including 6 data for each point. + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_PushPathPoints(unsigned int boxID, unsigned int rbtID, string sPathName, string sPoints); + +/** + * @brief: End pushing points to the path and start calculating the pathJ/L + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : Path name + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_EndPushPathPoints(unsigned int boxID,unsigned int rbtID, string sPathName); + +/** + * @brief: Delete the Path with the specified name + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : Path name + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_DelPath(unsigned int boxID, unsigned int rbtID, string sPathName); + +/** + * @brief: Read all path lists + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param result : Path name list + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadPathList(unsigned int boxID, unsigned int rbtID, string &result); + +/** + * @brief: Read the information of the specified name path + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : Path name + * @param result : Path name list + * + * nRawDataType:Raw point type, 0 for ACS, 1 for PCS + * stateJ:PathJ status: Not taught (0), Teaching in progress (1), Teaching completed (4), Calculating (2), Calculation error (5), + * Calculation completed (3), Imported (9), Post-import processing ( 10) + * + * ErrorCodeJ:PathJ error code + * stateL:The state of PathL, the same as stateJ + * ErrorCodeL:PathL error code + * dOverride:Path speed ratio + * dRadius:Blending radius, mm + * dLinearVel:Velocity for Path + * dLinearAcc: Acceleration for Path + * dLinearJerk:Jerk for Path + * dUcs_X-dUcs_Rz:User coordinates + * dTcp_X-dTcp_Rz:Tool coordinates + * nRawPointCount:Raw points count + * HeadPoint[]:The first raw point coordinates (J1/J2/J3/J4/J5/J6 or X/Y/Z/RX/RY/RZ) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadPathInfo(unsigned int boxID, unsigned int rbtID, string sPathName, string &result); + +/** + * @brief: Update the name of the specified path + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : Raw path name + * @param sPathNewName : New path name + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_UpdatePathName(unsigned int boxID, unsigned int rbtID, string sPathName, string sPathNewName); + +/** + * @brief: Read the status of the path + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : Path name + * @param nStateJ : nStateJ, refer to HRIF_ReadPathInfo + * @param nErrorCodeJ : nErrorCodeJ + * @param nStateL : nStateL, refer to HRIF_ReadPathInfo + * @param nErrorCodeL : nErrorCodeL + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadPathState(unsigned int boxID,unsigned int rbtID,string sPathName,int &nStateJ,int &nErrorCodeJ,int &nStateL,int &nErrorCodeL); + +/** + * @brief: Run PathJ + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : Path name + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MovePathJ(unsigned int boxID, unsigned int rbtID, string sPathName); + +/** + * @brief: Run PathL + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : Path name + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MovePathL(unsigned int boxID,unsigned int rbtID, string sPathName); + +/** + * @brief: Start to push ACS points for MovePathJ + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : MovePathJ name + * @param dSpeedRatio : Speed override + * @param dRadius : Blending radius, mm + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_StartPushMovePathJ(unsigned int boxID, unsigned int rbtID, string sPathName, double dSpeedRatio, double dRadius); + +/** + * @brief: Push an ACS raw point to MovePathJ. 4 points at lease are needed. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : MovePathJ name + * @param J1~6 : joint positions + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_PushMovePathJ(unsigned int boxID, unsigned int rbtID, string sPathName, double dJ1, double dJ2, double dJ3, double dJ4, double dJ5, double dJ6); + +/** + * @brief: End pushing ACS raw point to MovePathJ and start calculating the path + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : MovePathJ name + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_EndPushMovePathJ(unsigned int boxID, unsigned int rbtID, string sPathName); + +/** + * @brief: End pushing PCS raw point to MovePathL and start calculating the path + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : MovePathL name + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_EndPushMovePath(unsigned int boxID, unsigned int rbtID, string sPathName); + +/** + * @brief: Read status of MovePathJ + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : MovePathJ name + * @param nResult : MovePathJ status + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadMovePathJState(unsigned int boxID, unsigned int rbtID, string sPathName, int& nResult); + +/** + * @brief: Update MovePathJ name + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : MovePathJ name + * @param sPathNewName : New MovePathJ name + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_UpdateMovePathJName(unsigned int boxID, unsigned int rbtID, string PathName, string sPathNewName); + +/** + * @brief: Delete a MovePathJ + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : MovePathJ name + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_DelMovePathJ(unsigned int boxID, unsigned int rbtID, string sPathName); + +/** + * @brief: Initialize MovePathL + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : MovePathL name + * @param dVelocity : Velocity + * @param dAcceleration : Acceleration + * @param jerk : Jerk + * @param sUcsName : UCS name + * @param sTcpName : TCP name + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_InitMovePathL(unsigned int boxID, unsigned int rbtID, string sPathName, double dVelocity, double dAceleration, double djerk, string sUcsName, string sTcpName); + +/** + * @brief: Push an PCS point to MovePathL + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : MovePathL name + * @param dX~Rz : Raw point pose + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_PushMovePathL(unsigned int boxID, unsigned int rbtID, string sPathName, double dX, double dY, double dZ, double dRX, double dRY, double dRZ); + +/** + * @brief: Push an list of ACS or PCS points to MovePathJ or MovePathL + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param sPathName : MovePathJ or MovePathL name + * @param nMoveType : Move type, 0 for MovePathJ, 1 for MovePathL + * @param nPointsSize : Points number + * @param sPoints : Points list, separated by comma + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_PushMovePaths(unsigned int boxID, unsigned int rbtID, string sPathName, int nMoveType, int nPointsSize, string sPoints); + +/** + * @brief: Set Override for MovePathL + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dOverride: Override + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetMovePathOverride(unsigned int boxID, unsigned int rbtID, double dOverride); + +/** + * @brief: Read process and point index of a path move + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dProcess : Current path process, 0~1, more than 0.999999 for move done + * @param nIndex : Point index finished + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadTrackProcess(unsigned int boxID, unsigned int rbtID, double& dProcess, int& nIndex); + +/** + * @brief Structure of parameters required for MovePathJOL motion points,This structure is specifically for the HRIF_MovePathJOL interface + */ +typedef struct _MovePathJOLPointsData +{ + /** + * @brief Joint target position of axis 1 + */ + double dbJ1; + /** + * @brief Joint target position of axis 2 + */ + double dbJ2; + /** + *@brief Joint target position of axis 3 + */ + double dbJ3; + /** + * @brief Joint target position of axis 4 + */ + double dbJ4; + /** + * @brief Joint target position of axis 5 + */ + double dbJ5; + /** + * @brief Joint target position of axis 6 + */ + double dbJ6; + /** + * @brief Whether IO is set / set IO + */ + int nIsSetIO; + /** + * @brief The EndDO to be modified is identified by bit + */ + int nEndDOMask; + /** + * @brief The target state of each EndDO to be modified. + */ + int nEndDOVal; + /** + * @brief The BoxDO to be modified is identified by bit. + */ + int nBoxDOMask; + /** + * @brief The target state of each BoxDO to be modified. + */ + int nBoxDOVal; + /** + * @brief The BoxCO to be modified is identified by bit + */ + int nBoxCOMask; + /** + * @brief The target state of each BoxCO to be modified + */ + int nBoxCOVal; + /** + * @brief Indicator of whether BoxAOCH0 needs to be modified + */ + int nBoxAOCH0_Mask; + /** + * @brief Mode + */ + int nBoxAOCH0_Mode; + /** + * @brief Indicator of whether BoxAOCH1 needs to be modified + */ + int nBoxAOCH1_Mask; + /** + * @brief Mode + */ + int nBoxAOCH1_Mode; + /** + * @brief Corresponding analog value + */ + double dbBoxAOCH0_Val; + /** + * @brief Corresponding analog value + */ + double dbBoxAOCH1_Val; + _MovePathJOLPointsData():dbJ1(0),dbJ2(0),dbJ3(0),dbJ4(0),dbJ5(0),dbJ6(0),nIsSetIO(0),nEndDOMask(0),nEndDOVal(0),nBoxDOMask(0),nBoxDOVal(0),nBoxCOMask(0),nBoxCOVal(0),nBoxAOCH0_Mask(0),nBoxAOCH0_Mode(0),nBoxAOCH1_Mask(0),nBoxAOCH1_Mode(0),dbBoxAOCH0_Val(0),dbBoxAOCH1_Val(0) + { + } +}MovePathJOLPointsData; + +/** + * @brief: Start MovePathJ for online implementation planning + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dVel : Joint velocitie + * @param dAcc : Joint acceleration + * @param dTol : Filter parameter + * @param JOLPointsData : MovePathJOL motion point data. MovePathJOLPointsData is a structure that contains various data required for setting the MovePathJOL motion points + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MovePathJOL(unsigned int boxID, unsigned int rbtID, int dVel, int dAcc, int dTol,vector JOLPointsData); + +/** + * @brief: Obtain the index number of the current point position of MovePathJOL motion and the total number of points in the trajectory motion + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nCurIndex : Point index finished + * @param nIndexNum : The total number of points in the current trajectory movement + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GetMovePathJOLIndex(unsigned int boxID, unsigned int rbtID, int& nCurIndex, int& nIndexNum); + +/** + * @brief: Set the reference joint coordinates for space point trajectory + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param strPathName : Path name + * @param dJ1-dJ6 : Reference joint coordinates + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetPathRefJoints(unsigned int boxID, unsigned int rbtID, string& strPathName, double dJ1, double dJ2, double dJ3, double dJ4, double dJ5, double dJ6); + +//************************************************************************/ +//** Interfaces for Servo +//************************************************************************/ +/** + * @brief: Start Servo J/P with the specified ServoTime of update and LookaheadTime. This function is a member of the suite including HRIF_StartServo, HRIF_PushServoJ and HRIF_PushServoP. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dServoTime : interval of update (s) + * @param dLookaheadTime : LookaheadTime (s) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_StartServo(unsigned int boxID, unsigned int rbtID, double dServoTime, double dLookaheadTime); + +/** + * @brief: Push point of joint positions to robot with the ServoTime and LookaheadTime specified in HRIF_StartServo function, and the robot will track the joint positions in real time. This function is a member of the suite including HRIF_StartServo, HRIF_PushServoJ and HRIF_PushServoP. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param J1~6 : Joint positions + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_PushServoJ(unsigned int boxID, unsigned int rbtID, double dJ1, double dJ2, double dJ3, double dJ4, double dJ5, double dJ6); + +/** + * @brief: Push pose point to robot with the ServoTime and LookaheadTime specified in HRIF_StartServo function, and the robot will track the pose in real time. This function is a member of the suite including HRIF_StartServo, HRIF_PushServoJ and HRIF_PushServoP. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param vecCoord : pose list + * @param vecUcs : UCS list + * @param vecTcp : TCP list + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_PushServoP(unsigned int boxID, unsigned int rbtID, vector& vecCoord, vector& vecUcs, vector& vecTcp); + +/** + * @brief: In SpeedJ mode,Push joint command velocity + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dJ1CmdVel~dJ6CmdVel : Joint speed, °/s + * @param dAcc : Joint Acc, °/s^2 + * @param dRuntime : Command execution exceeds this time, the movement will stop, s + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SpeedJ(unsigned int boxID, unsigned int rbtID, double dJ1CmdVel,double dJ2CmdVel,double dJ3CmdVel,double dJ4CmdVel, + double dJ5CmdVel,double dJ6CmdVel,double dAcc,double dRuntime); + +/** + * @brief: In SpeedL mode,Push position command velocity + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dXCmdVel~dZCmdVel : position velocity, mm/s + * @param dRxCmdVel~dRzCmdVel : angular velocity, °/s + * @param dLinearAcc : linear Acc, mm/s^2 + * @param dAngularAcc : angular Acc, °/s^2 + * @param dRuntime : Command execution exceeds this time, the movement will stop, s + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SpeedL(unsigned int boxID, unsigned int rbtID, double dXCmdVel,double dYCmdVel,double dZCmdVel,double dRxCmdVel, + double dRyCmdVel,double dRzCmdVel,double dLinearAcc,double dAngularAcc,double dRuntime); + +/** + * @brief: Initialize servoEsJ, truncating points + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_InitServoEsJ(unsigned int boxID, unsigned int rbtID); + +/** + * @brief: Start move for servoEsJ + * @param boxID : control box number, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dServoTime : interval of update (s) + * @param dLookaheadTime : LookaheadTime (s) + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_StartServoEsJ(unsigned int boxID, unsigned int rbtID, double dServoTime, double dLookheadTime); + +/** + * @brief: Push points list for servoEsJ + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nPointsSize: Point size, maximum 500 + * @param sPoints : Points list, separated by "," + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_PushServoEsJ(unsigned int boxID, unsigned int rbtID, int nPointsSize, string sPoints); + +/** + * @brief: Read servoEsJ state with call interval more than 20 ms + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nState: Pushing point is allowed or not; 0 for allowed, 1 for not allowed + * @param nIndex: Current point index reached + * @param nCount: Points number finished + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadServoEsJState(unsigned int boxID, unsigned int rbtID, int& nState, int& nIndex, int& nCount); + +/** + * @brief: Set motion parameters of tracking motion + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nState: 0 to disable tracking, 1 to enable tracking + * @param dDistance: Relative distance of tracking + * @param dAwayVelocity: Away velocity of tracking + * @param dGobackVelocity: Back velocity of tracking + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetMoveTraceParams(unsigned int boxID, unsigned int rbtID, int nState, double dDistance, double dAwayVelocity, double dGobackVelocity); + +/** + * @brief: Set initial parameters of tracking motion + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dK,dB: parameters in equation as y = dK * x + dB + * @param dMaxLimit: Max distance detected by laser sensor + * @param dMinLinit: Min distance detected by laser sensor + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetMoveTraceInitParams(unsigned int boxID, unsigned int rbtID, double dK, double dB, double dMaxLimit, double dMinLinit); + +/** + * @brief: Set end orientation in tracking motion + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Z: invalid, 0 recommended + * @param dRx~Rz: End orientation in specified UCS through function HRIF_SetUCSByName + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetMoveTraceUcs(unsigned int boxID, unsigned int rbtID, double dX, double dY, double dZ, double dRx, double dRy, double dRz); + +/** + * @brief: Set tracking motion state + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nState: 0 to disable tracking, 1 to enable tracking + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetTrackingState(unsigned int boxID, unsigned int rbtID, int nState); + +/** + * @brief: Excute HRApp command + * @param boxID : Control box ID, 0 as default + * @param AppName: AppName + * @param sCmdName: Command name + * @param sParams: Parameters list + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_HRAppCmd(unsigned int boxID,string AppName, string sCmdName, string sParams); + +/** + * @brief: Excute HRApp command + * @param boxID : Control box ID, 0 as default + * @param AppName: AppName + * @param sCmdName: Command name + * @param sParams: Parameters list + * @param sResult: Result list + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_HRAppData(unsigned int boxID,string AppName, string sCmdName, string sParams, vector& sResults); + +/** + * @brief: Write End holding registers of Modbus slave + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nSlaveID: Modbus slave ID + * @param nFunction: Function code + * @param nRegAddr: Register beginning address + * @param nRegCount: Registers number, maximum 11 + * @param vecData: Registers value, with the registers size of nRegCount + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_WriteEndHoldingRegisters(unsigned int boxID, unsigned int rbtID, int nSlaveID, int nFunction, int nRegAddr, int nRegCount, vector vecData); + +/** + * @brief: Read End holding registers of Modbus slave + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nSlaveID: Modbus slave ID + * @param nFunction: Function code + * @param nRegAddr: Register beginning address + * @param nRegCount: Registers number, maximum 12 + * @param vecData: obtained registers value, with the registers size of nRegCount + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadEndHoldingRegisters(unsigned int boxID, unsigned int rbtID, int nSlaveID, int nFunction, int nRegAddr, int nRegCount, vector& vecData); + +/** + * @brief: Set Modbus configuration for end connection + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nBaudrate: Baud rate, 115200 as default + * @param nDataBit: Data bits, 8 as default + * @param nParityBit: Parity bit (78: No parity; 79: Odd parity; 69: Even parity) + * @param nStopBit: Stop bits, 1 as default + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetEndModbusInfo(unsigned int boxID, unsigned int rbtID, int nBaudrate, int nDataBit, int nParityBit, int nStopBit); + +/** + * @brief: Set max velocity of tracking position + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dMaxLineVel: Max follow position velocity, mm/s + * @param dMaxOriVel: Max follow angular velocity, °/s + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetPoseTrackingMaxMotionLimit(unsigned int boxID, unsigned int rbtID, + double dMaxLineVel,double dMaxOriVel); + +/** + * @brief: Set location tracking timeout stop time + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dTime: time out + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetPoseTrackingStopTimeOut(unsigned int boxID, unsigned int rbtID, double dTime); + +/** + * @brief: Set PID of tracking position + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dPosPID1-dPosPID3: Follow position PID + * @param dOriPID1-dOriPID3: Follow angular PID + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetPoseTrackingPIDParams(unsigned int boxID, unsigned int rbtID, + double dPosPID1,double dPosPID2,double dPosPID3, + double dOriPID1,double dOriPID2,double dOriPID3); + +/** + * @brief: Set target of tracking position + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX-dRz: Target Position + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetPoseTrackingTargetPos(unsigned int boxID, unsigned int rbtID, + double dX,double dY,double dZ,double dRx,double dRy,double dRz); + +/** + * @brief: Set state of tracking position + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nState: 0: Disable tracking position + * 1: Enable tracking position + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetPoseTrackingState(unsigned int boxID, unsigned int rbtID, int nState); + +/** + * @brief:Set real time position update of tracking position + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX-dRz: Checking distance + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetUpdateTrackingPose(unsigned int boxID, unsigned int rbtID, + double dX,double dY,double dZ,double dRx,double dRy,double dRz); + +/** + * @brief: Confige TCP + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX-dRz: TCP Position + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ConfigTCP(unsigned int boxID, unsigned int rbtID, + string sTcpName,double dX, double dY, double dZ, double dRx, double dRy, double dRz); + +/** + * @brief: Confige UCS + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX-dRz: UCS Position + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ConfigUCS(unsigned int boxID, unsigned int rbtID, + string strName,double x,double y,double z,double rx,double ry,double rz); + +/** + * @brief:Move to target pose + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX-dRz: target pose + * @param strTCPName: TCP name + * @param strUCSName: UCS name + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +//ROBOT_PRO_API int HRIF_MoveLTo(unsigned int boxID, unsigned int nRbtID, +// double dX, double dY, double dZ, double dRx, double dRy, double dRz, string strTCPName, string strUCSName); + +/** + * @brief: Move to target joint positions + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param J1-J6: target positions + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +//ROBOT_PRO_API int HRIF_MoveJTo(unsigned int boxID, unsigned int nRbtID, +// double J1, double J2, double J3, double J4, double J5, double J6); + +/** + * @brief: Move to safe space + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveToSS(unsigned int boxID, unsigned int rbtID); + +/** + * @brief: set the collision sensitivity level + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nSafeLevel : sensitivity level, 0-5, 0 for the highest, 5 for the lowest + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetCollideLevel(unsigned int boxID, unsigned int rbtID,int nSafeLevel); + +/** + * @brief: read the collision sensitivity level + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nSafeLevel : sensitivity level, 0-5, 0 for the highest, 5 for the lowest + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GetCollideLevel(unsigned int boxID, unsigned int rbtID,int &nSafeLevel); + +/** + * @brief:Set EtherCatAO + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nIndex: AO + * @param dValue: Analog value + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetECATAO(unsigned int boxID, unsigned int rbtID, int nIndex, double dValue); + + +/** + * @brief:Add a new safe plane + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param Name: plane name + * @param UcsName:UCS Name + * @param Mode: Mode + * @param Display: Display + * @param Switch: Enable + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_AddSafePlane(unsigned int boxID, unsigned int rbtID, string Name, string UcsName , int Mode, int Display, int Switch); + +/** + * @brief:Delete Safe Plane + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param Name: plane name + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_DelSafePlane(unsigned int boxID, unsigned int rbtID, string Name); + + +/** + * @brief:Get SafePlane List + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param BordersName: plane name + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadSafePlaneList(unsigned int boxID, unsigned int rbtID, vector &BordersName); + +/** + * @brief:Read SafePlane Info + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param Name: plane name + * @param UcsName:UCS Name + * @param Mode: Mode + * @param Display: Display + * @param Switch: Enable + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadSafePlane(unsigned int boxID, unsigned int rbtID, string Name, string &UcsName , int &Mode, int &Display, int &Switch); + +/** + * @brief:Update SafePlane Info + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param Name: plane name + * @param UcsName:UCS Name + * @param Mode: Mode + * @param Display: Display + * @param Switch: Enable + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_UpdateSafePlane(unsigned int boxID, unsigned int rbtID, string Name, string UcsName , int Mode, int Display, int Switch); + +/** + * @brief:Set the distance threshold when the virtual wall begins to generate damping + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dDepth: Threshold size + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetDepthThresholdForDampingArea(unsigned int boxID, unsigned int rbtID, double dDepth); + + + +//************************************************************************/ +//** Interfaces for Extended Axis +//************************************************************************/ + +struct JointsData +{ + double dJoint[MaxJointNums]={0}; +}; + +/** + * @brief: Set joint max velocity + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param joints : Joint1~n max velocity, °/s + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetJointMaxVel_nJ(unsigned int boxID, unsigned int rbtID, JointsData joints); + + +/** + * @brief: Set joint max acceleration + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param joints : Joint1~n max acceleration + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetJointMaxAcc_nJ(unsigned int boxID, unsigned int rbtID, JointsData joints); + + +/** + * @brief: Set max range of joint motion + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param jdMaxJoints : Max joint angle + * @param jdMinJoints : Min joint angle + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetMaxAcsRange_nJ(unsigned int boxID, unsigned int rbtID, JointsData jdMaxJoints, JointsData jdMinJoints); + + +/** + * @brief: Read command joint positions + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param joints : Joint command positions + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadCmdJointPos_nJ(unsigned int boxID, unsigned int rbtID, JointsData& joints); + +/** + * @brief: Read command current of joint + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param joints : Joint command current + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadCmdJointCur_nJ(unsigned int boxID, unsigned int rbtID, JointsData& joints); + +/** + * @brief: Read actual current of joint + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param joints : Joint actual current + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadActJointCur_nJ(unsigned int boxID, unsigned int rbtID, JointsData& joints); + +/** + * @brief: Read actual joint positions + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param joints : Joint actual positions + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadActJointPos_nJ(unsigned int boxID, unsigned int rbtID, JointsData& joints); + +/** + * @brief: Read command velocity of joint + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param joints : Joint command velocity + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadCmdJointVel_nJ(unsigned int boxID, unsigned int rbtID, JointsData& joints); + +/** + * @brief: Read actual velocity of joint + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param joints : Joint actual velocity + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadActJointVel_nJ(unsigned int boxID, unsigned int rbtID, JointsData& joints); + +/** + * @brief: Inverse kinematics transformation from pose to joint positions + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dCoord_X~Rz : pose in specified UCS and TCP + * @param dTcp_X~Rz : TCP pose + * @param dUcs_X~Rz : UCS pose + * @param joints : Reference joint positions used for the selection in multiple solutions + * @param dTargetJoints : Inverse solution of J1~Jn + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GetInverseKin_nJ(unsigned int boxID, unsigned int rbtID, double dCoord_X, double dCoord_Y, double dCoord_Z, double dCoord_Rx, double dCoord_Ry, double dCoord_Rz, + double dTcp_X, double dTcp_Y, double dTcp_Z, double dTcp_Rx, double dTcp_Ry, double dTcp_Rz, + double dUcs_X, double dUcs_Y, double dUcs_Z, double dUcs_Rx, double dUcs_Ry, double dUcs_Rz, + JointsData joints, + JointsData &dTargetJoints); + +/** + * @brief: Forward kinematics transformation from joint positions to pose + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param joints : joint positions + * @param dTcp_X~Rz : TCP pose for the target + * @param dUcs_X~Rz : UCS pose for the target + * @param dTarget_X~Rz : pose in specified UCS and TCP + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GetForwardKin_nJ(unsigned int boxID, unsigned int rbtID, JointsData joints, + double dTcp_X, double dTcp_Y, double dTcp_Z, double dTcp_Rx, double dTcp_Ry, double dTcp_Rz, + double dUcs_X, double dUcs_Y, double dUcs_Z, double dUcs_Rx, double dUcs_Ry, double dUcs_Rz, + double &dTarget_X, double &dTarget_Y, double &dTarget_Z, double &dTarget_Rx, double &dTarget_Ry, double &dTarget_Rz); + +/** + * @brief: Start relative wayPoint + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nType : Move type, 0 for joint, 1 for linear + * @param nPointList : Using point saved in points list or not, 0 for not using, 1 for using + * @param dPos_X~Rz : Target pose + * @param dJointsPos : Target joint positions + * @param nrelMoveType : Relative move type, 0 for absolute value, 1 for superimposed value + * @param nAxisMask : Is Axis/Direction Moving, 0 for not moving, 1 for moving + * @param dTarget : Moving distance; invalid when nAxisMask equals 0; joint move an absolute or superimposed distance when nAxisMask equals 1 and nType equals 0; Pose move an absolute or superimposed distance when nAxisMask equals 1 and nType equals 1. + * @param sTcpName : Target TCP name, invalid when IsUseJoint equals 1, "TCP" is valid as default + * @param sUcsName : Target UCS name, invalid when IsUseJoint equals 1, "Base" is valid as default + * @param dVelocity: Velocity (mm/s) or (°/s) + * @param dAcc: Acceleration (mm/s^2) or (°/s^2) + * @param dRadius: Blending radius, mm + * @param nIsUseJoint : Using joint or not, usable when MoveType equals 0, 0 for not using joint, 1 for using joint + * @param nIsSeek,nIOBit,nIOState:Seeking or not, 1 for seeking; Motion stops when DO-nIOBit equals nIoState + * @param strCmdID:Command ID, waypoint ID also, customized or set to 1, 2, 3 in order + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_WayPointRel_nJ(unsigned int boxID, unsigned int rbtID, + int nType, + int nPointList, + double dPos_X, double dPos_Y, double dPos_Z, double dPos_Rx, double dPos_Ry, double dPos_Rz, + JointsData dJointsPos, + int nrelMoveType, + JointsData nAxisMask, + JointsData dTarget, + string sTcpName, string sUcsName, + double dVelocity, double dAcc, double dRadius, + int nIsUseJoint, int nIsSeek, int nIOBit, int nIOState, string strCmdID); + +/** + * @brief: Start joint move. HRIF_WayPoint is more recommended yet + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz : Target pose, invalid when IsUseJoint equals 1. Target joint positions is obtained through inversely solving from this pose when IsUseJoint equals 0 + * @param joints : Target joint positions when IsUseJoint equals 1; Reference joint positions for the inversely solving when IsUseJoint equals 0. + * @param sTcpName : Target TCP name, invalid when IsUseJoint equals 1, "TCP" is valid as default + * @param sUcsName : Target UCS name, invalid when IsUseJoint equals 1, "Base" is valid as default + * @param dVelocity : Motion speed, mm/s or °/s + * @param dAcc : Motion acceleration, mm/(s^2) or °/(s^2) + * @param dRadius : Blending radius, mm + * @param nIsUseJoint : Using joint or not, usable when MoveType equals 0, 0 for not using joint, 1 for using joint + * @param nIsSeek,nIOBit,nIOState:Seeking or not, 1 for seeking; Motion stops when DO-nIOBit equals nIoState + * @param strCmdID:Command ID, waypoint ID also, customized or set to 1, 2, 3 in order + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveJ_nJ(unsigned int boxID, unsigned int rbtID, double dX, double dY, double dZ, double dRx, double dRy, double dRz, + JointsData joints, + string sTcpName, string sUcsName, double dVelocity, double dAcc, double dRadius, + int nIsUseJoint,int nIsSeek, int nIOBit, int nIOState, string strCmdID); + +/** + * @brief: Start linear move. HRIF_WayPoint is more recommended yet + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz : Target pose + * @param joints : Reference joint positions near to the target pose + * @param sTcpName : TCP name + * @param sUcsName : UCS name + * @param dVelocity : Motion speed, mm/s or °/s + * @param dAcc : Motion acceleration, mm/(s^2) or °/(s^2) + * @param dRadius :Blending radius, mm + * @param nIsSeek,nIOBit,nIOState:Seeking or not, 1 for seeking; Motion stops when DO-nIOBit equals nIoState + * @param strCmdID:Command ID, waypoint ID also, customized or set to 1, 2, 3 in order + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveL_nJ(unsigned int boxID, unsigned int rbtID, double dX, double dY, double dZ, double dRx, double dRy, double dRz, + JointsData joints, + string sTcpName, string sUcsName, double dVelocity, double dAcc, double dRadius, + int nIsSeek, int nIOBit, int nIOState, string strCmdID); + +/** + * @brief: Start linear move. HRIF_WayPoint is more recommended yet + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz : Target pose + * @param joints : Reference joint positions near to the target pose + * @param sTcpName : TCP name + * @param sUcsName : UCS name + * @param dVelocity : Motion speed, mm/s or °/s + * @param dAcc : Motion acceleration, mm/(s^2) or °/(s^2) + * @param dRadius :Blending radius, mm + * @param nIsSeek,nIOBit,nIOState:Seeking or not, 1 for seeking; Motion stops when DO-nIOBit equals nIoState + * @param strCmdID:Command ID, waypoint ID also, customized or set to 1, 2, 3 in order + * @param lockedFlags : Joint locking flags (bitmask). Bits 6-9 represent external axes J7-J10. Set bit to 1 to lock the corresponding axis, 0 to unlock. + * Example values: Lock J7: 0x40 (64 in decimal),Lock J7 and J8: 0xC0 (192 in decimal),Valid range: The joints that can be locked need to be determined by the specific controller version. + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveLByLockAxis_nJ(unsigned int boxID, unsigned int rbtID, double dX, double dY, double dZ, double dRx, double dRy, double dRz, + JointsData joints, + string sTcpName, string sUcsName, double dVelocity, double dAcc, double dRadius, + int nIsSeek, int nIOBit, int nIOState, string strCmdID,int lockedFlags); + +/** + * @brief: Start round move + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dStartPos_X~Rz : Start pose + * @param dAuxPos_X~Rz : Via pose + * @param dEndPos_X~Rz : End pose + * @param nFixedPosure : 0 for unfixed orientation, 1 for fixed orientation, 2 for newly added gradual orientation arc motion. + * + * @param nMoveCType : 1 for arc, 0 for circle + * @param dRadLen : Invalid when nMoveCType equals 1; Circles when nMoveCType equals 0. + * + * @param dVelocity : Motion speed, mm/s + * @param dAcc : Motion acceleration, mm/(s^2) + * @param dRadius : Blending radius, mm + * @param sTcpName : TCP name + * @param sUcsName : UCS name + * @param sCmdID : Command ID + * @param joints : The reference joint position of the end point + * @param nIsUseCurRefACS : Whether to use the reference joint Angle + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_MoveC_nJ(unsigned int boxID, unsigned int rbtID, + double dStartPos_X,double dStartPos_Y,double dStartPos_Z,double dStartPos_Rx,double dStartPos_Ry,double dStartPos_Rz, + double dAuxPos_X,double dAuxPos_Y,double dAuxPos_Z,double dAuxPos_Rx,double dAuxPos_Ry,double dAuxPos_Rz, + double dEndPos_X,double dEndPos_Y,double dEndPos_Z,double dEndPos_Rx,double dEndPos_Ry,double dEndPos_Rz, + int nFixedPosure,int nMoveCType,double dRadLen,double dVelocity, double dAcc, + double dRadius, string sTcpName, string sUcsName, string sCmdID, JointsData joints,int nIsUseCurRefACS = 0); + +/** + * @brief: Start round move + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dStartPos_X~Rz : Start pose + * @param dAuxPos_X~Rz : Via pose + * @param dEndPos_X~Rz : End pose + * @param nFixedPosure : 0 for unfixed orientation , 1 for fixed orientation + * @param nMoveCType : 1 for arc, 0 for circle + * @param dRadLen : Invalid when nMoveCType equals 1; Circles when nMoveCType equals 0. + * @param dVelocity : Motion speed, mm/s + * @param dAcc : Motion acceleration, mm/(s^2) + * @param dRadius : Blending radius, mm + * @param sTcpName : TCP name + * @param sUcsName : UCS name + * @param sCmdID : Command ID + * @param joints : The reference joint position of the end point + * @param lockedFlags : Joint locking flags (bitmask). Bits 6-9 represent external axes J7-J10. Set bit to 1 to lock the corresponding axis, 0 to unlock. + * Example values: Lock J7: 0x40 (64 in decimal),Lock J7 and J8: 0xC0 (192 in decimal),Valid range: The joints that can be locked need to be determined by the specific controller version. + * @param nIsUseCurRefACS : Whether to use the reference joint Angle + * + * @return : nRet=0 : Function call succeeded / 函数调用成功 + * nRet>0 : Error code of function call / 函数调用失败的错误码 + */ +ROBOT_PRO_API int HRIF_MoveCByLockAxis_nJ(unsigned int boxID, unsigned int rbtID, + double dStartPos_X,double dStartPos_Y,double dStartPos_Z,double dStartPos_Rx,double dStartPos_Ry,double dStartPos_Rz, + double dAuxPos_X,double dAuxPos_Y,double dAuxPos_Z,double dAuxPos_Rx,double dAuxPos_Ry,double dAuxPos_Rz, + double dEndPos_X,double dEndPos_Y,double dEndPos_Z,double dEndPos_Rx,double dEndPos_Ry,double dEndPos_Rz, + int nFixedPosure,int nMoveCType,double dRadLen,double dVelocity, double dAcc, + double dRadius, string sTcpName, string sUcsName, string sCmdID, JointsData joints, int lockedFlags, int nIsUseCurRefACS = 0); + +/** + * @brief: Read joint max velocity + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param joints : Max velocity of J1~Jn + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadJointMaxVel_nJ(unsigned int boxID, unsigned int rbtID, JointsData &joints); + +/** + * @brief: Read joint max jerk + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param joints : Max jerk of J1~Jn + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadJointMaxJerk_nJ(unsigned int boxID, unsigned int rbtID, JointsData &joints); + +/** + * @brief: Read joint max acceleration + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param joints : Max acceleration of J1~Jn + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadJointMaxAcc_nJ(unsigned int boxID, unsigned int rbtID, JointsData &joints); + +/** + * @brief: Read point pose, joint positions, UCS and TCP by point name + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param joints : joint positions + * @param dX~Rz : pose + * @param dTcp_X~Rz : TCP pose + * @param dUcs_X~Rz : UCS pose + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadPointByName_nJ(unsigned int boxID, unsigned int rbtID, string strPointName, + JointsData &joints, + double& dX, double& dY, double& dZ, double& dRx, double& dRy, double& dRz, + double& dTcp_X, double& dTcp_Y, double& dTcp_Z, double& dTcp_Rx, double& dTcp_Ry, double& dTcp_Rz, + double& dUcs_X, double& dUcs_Y, double& dUcs_Z, double& dUcs_Rx, double& dUcs_Ry, double& dUcs_Rz); + +/** + * @brief: Read actual pose and joint positions + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX~Rz : pose + * @param joints: joint positions + * @param dTcp_X~Rz : TCP pose + * @param dUcs_X~Rz : UCS pose + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadActPos_nJ(unsigned int boxID, unsigned int rbtID, + double &dX, double &dY, double &dZ, double &dRx, double &dRy, double &dRz, + JointsData &joints, + double &dTcp_X, double &dTcp_Y, double &dTcp_Z, double &dTcp_Rx, double &dTcp_Ry, double &dTcp_Rz, + double &dUcs_X, double &dUcs_Y, double &dUcs_Z, double &dUcs_Rx, double &dUcs_Ry, double &dUcs_Rz); + +/** + * @brief: Start waypoint move. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nMoveType : Move type, 0 for MoveJ, 1 for MoveL + * @param dX~Rz : Target pose, invalid when IsUseJoint equals 1. Target joint positions is obtained through inversely solving from this pose when IsUseJoint equals 0 + * @param joints : Target joint positions when IsUseJoint equals 1; Reference joint positions for the inversely solving when IsUseJoint equals 0. + * @param dTcp_X~Rz : Target TCP, invalid when IsUseJoint equals 1 + * @param dUcs_X~Rz : Target UCS, invalid when IsUseJoint equals 1 + * @param dVelocity : Motion speed, mm/s or °/s + * @param dAcc : Motion acceleration, mm/(s^2) or °/(s^2) + * @param dRadius : Blending radius, mm + * @param nIsUseJoint : Using joint or not, usable when MoveType equals 0, 0 for not using joint, 1 for using joint + * @param nIsSeek,nIOBit,nIOState:Seeking or not, 1 for seeking; Motion stops when DO-nIOBit equals nIoState + * @param strCmdID:Command ID, waypoint ID also, customized or set to 1, 2, 3 in order + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_WayPointEx_nJ(unsigned int boxID, unsigned int rbtID, int nMoveType, + double dX, double dY, double dZ, double dRx, double dRy, double dRz, + JointsData joints, + double dTcp_X, double dTcp_Y, double dTcp_Z, double dTcp_Rx, double dTcp_Ry, double dTcp_Rz, + double dUcs_X, double dUcs_Y, double dUcs_Z, double dUcs_Rx, double dUcs_Ry, double dUcs_Rz, + double dVelocity, double dAcc, double dRadius, int nIsUseJoint, int nIsSeek, int nIOBit, int nIOState, string strCmdID); + +/** + * @brief: Start waypoint move. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nMoveType : Move type, 0 for MoveJ, 1 for MoveL, 2 for MoveC + * @param dEndPos_X~Rz : Target pose, invalid when IsUseJoint equals 1. Target pose is obtained through inverse kinematics from this pose when IsUseJoint equals 0 +* @param dAuxPos_X~Rz : Target auxiliary pose invalid when IsUseJoint equals 1, used as the middle point when nMoveType equals 2. Target pose is obtained through inverse kinematics from this pose when IsUseJoint equals 0 + * @param joints : Target joint positions when IsUseJoint equals 1; Reference joint positions for the inverse kinematics when IsUseJoint equals 0. + * @param sTcpName : Target TCP name, invalid when IsUseJoint equals 1, "TCP" is valid as default + * @param sUcsName : Target UCS name, invalid when IsUseJoint equals 1, "Base" is valid as default + * @param dVelocity : Motion speed, mm/s or °/s + * @param dAcc : Motion acceleration, mm/(s^2) or °/(s^2) + * @param dRadius : Blending radius, mm + * @param nIsUseJoint : Using joint or not, usable when MoveType equals 0, 0 for not using joint, 1 for using joint + * @param nIsSeek,nIOBit,nIOState:Seeking or not, 1 for seeking; Motion stops when DO-nIOBit equals nIoState + * @param strCmdID:Command ID, waypoint ID also, customized or set to 1, 2, 3 in order + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_WayPoint2_nJ(unsigned int boxID, unsigned int rbtID, int nMoveType, + double dEndPos_X, double dEndPos_Y, double dEndPos_Z, double dEndPos_Rx, double dEndPos_Ry, double dEndPos_Rz, + double dAuxPos_X, double dAuxPos_Y, double dAuxPos_Z, double dAuxPos_Rx, double dAuxPos_Ry, double dAuxPos_Rz, + JointsData joints, + string sTcpName, string sUcsName, double dVelocity, double dAcc, double dRadius, + int nIsUseJoint,int nIsSeek, int nIOBit, int nIOState, string strCmdID); + +/** + * @brief: Start waypoint move. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nMoveType : Move type, 0 for MoveJ, 1 for MoveL, 2 for MoveC + * @param dEndPos_X~Rz : Target pose, invalid when IsUseJoint equals 1. Target pose is obtained through inverse kinematics from this pose when IsUseJoint equals 0 +* @param dAuxPos_X~Rz : Target auxiliary pose invalid when IsUseJoint equals 1, used as the middle point when nMoveType equals 2. Target pose is obtained through inverse kinematics from this pose when IsUseJoint equals 0 + * @param joints : Target joint positions when IsUseJoint equals 1; Reference joint positions for the inverse kinematics when IsUseJoint equals 0. + * @param sTcpName : Target TCP name, invalid when IsUseJoint equals 1, "TCP" is valid as default + * @param sUcsName : Target UCS name, invalid when IsUseJoint equals 1, "Base" is valid as default + * @param dVelocity : Motion speed, mm/s or °/s + * @param dAcc : Motion acceleration, mm/(s^2) or °/(s^2) + * @param dRadius : Blending radius, mm + * @param nIsUseJoint : Using joint or not, usable when MoveType equals 0, 0 for not using joint, 1 for using joint + * @param nIsSeek,nIOBit,nIOState:Seeking or not, 1 for seeking; Motion stops when DO-nIOBit equals nIoStat + * @param strCmdID:Command ID, waypoint ID also, customized or set to 1, 2, 3 in order + * @param lockedFlags : Joint locking flags (bitmask). Bits 6-9 represent external axes J7-J10. Set bit to 1 to lock the corresponding axis, 0 to unlock. + * Example values: Lock J7: 0x40 (64 in decimal),Lock J7 and J8: 0xC0 (192 in decimal),Valid range: The joints that can be locked need to be determined by the specific controller version. + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_WayPoint2ByLockAxis_nJ(unsigned int boxID, unsigned int rbtID, int nMoveType, + double dEndPos_X, double dEndPos_Y, double dEndPos_Z, double dEndPos_Rx, double dEndPos_Ry, double dEndPos_Rz, + double dAuxPos_X, double dAuxPos_Y, double dAuxPos_Z, double dAuxPos_Rx, double dAuxPos_Ry, double dAuxPos_Rz, + JointsData joints, + string sTcpName, string sUcsName, double dVelocity, double dAcc, double dRadius, + int nIsUseJoint,int nIsSeek, int nIOBit, int nIOState, string strCmdID, int lockedFlags); + +/** + * @brief: Start waypoint move. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nMoveType : Move type, 0 for MoveJ, 1 for MoveL + * @param dX~Rz : Target pose, invalid when IsUseJoint equals 1. Target joint positions is obtained through inversely solving from this pose when IsUseJoint equals 0 + * @param joints : Target joint positions when IsUseJoint equals 1; Reference joint positions for the inversely solving when IsUseJoint equals 0. + * @param sTcpName : Target TCP name, invalid when IsUseJoint equals 1, "TCP" is valid as default + * @param sUcsName : Target UCS name, invalid when IsUseJoint equals 1, "Base" is valid as default + * @param dVelocity : Motion speed, mm/s or °/s + * @param dAcc : Motion acceleration, mm/(s^2) or °/(s^2) + * @param dRadius : Blending radius, mm + * @param nIsUseJoint : Using joint or not, usable when MoveType equals 0, 0 for not using joint, 1 for using joint + * @param nIsSeek,nIOBit,nIOState:Seeking or not, 1 for seeking; Motion stops when DO-nIOBit equals nIoState + * @param strCmdID:Command ID, waypoint ID also, customized or set to 1, 2, 3 in order + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_WayPoint_nJ(unsigned int boxID, unsigned int rbtID, int nMoveType, + double dX, double dY, double dZ, double dRx, double dRy, double dRz, + JointsData joints, + string sTcpName, string sUcsName, double dVelocity, double dAcc, double dRadius, + int nIsUseJoint,int nIsSeek, int nIOBit, int nIOState, string strCmdID); + +/** + * @brief: Read axis error code + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nErrorCode : Error code + * @param Params : The error code of J1~Jn, 0 if no error + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadAxisErrorCode_nJ(unsigned int boxID, unsigned int rbtID, int& nErrorCode, JointsData &nAxisErrorCode); + +/** + * @brief: Set default TCP (Tool Center Point) + * @param boxID : Control box ID, 0 by default + * @param rbtID : Robot ID, 0 by default + * @param name : Name of the TCP to be set as default + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Function call error code + */ +ROBOT_PRO_API int HRIF_SetDftTCP(unsigned int boxID, unsigned int rbtID,string name); + +/** + * @brief: Calculate TCP orientation + * @param boxID : Control box ID, 0 by default + * @param rbtID : Robot ID, 0 by default + * @param dUCS_X-dUCS_RZ : Coordinates and rotation angles in user coordinate system + * @param dTCP_X-dTCP_RZ : Coordinates and rotation angles of TCP + * @param RX-RZ : Calculated rotation angles + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Function call error code + */ +ROBOT_PRO_API int HRIF_CalTCPOrt(unsigned int boxID, unsigned int rbtID, double dUCS_X,double dUCS_Y,double dUCS_Z,double dUCS_RX,double dUCS_RY,double dUCS_RZ,double dTCP_X,double dTCP_Y,double dTCP_Z,double dTCP_RX,double dTCP_RY,double dTCP_RZ,double &RX,double&RY,double &RZ); + +/** + * @brief: Read current coordinates of the robot arm + * @details: Get joint coordinates and user coordinates of the robot arm in current state by inputting TCP and UCS names + * @param boxID : Control box ID, 0 by default + * @param rbtID : Robot ID, 0 by default + * @param UcsName : Name of the user coordinate system + * @param TcpName : Name of the tool center point + * @param dJ1-dJ6 : Current joint coordinates + * @param dX-dRz : Current user coordinates + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Function call error code + */ +ROBOT_PRO_API int HRIF_ReadActCoord(unsigned int boxID, unsigned int rbtID, string UcsName,string TcpName,double &dJ1,double &dJ2,double &dJ3,double &dJ4,double &dJ5,double &dJ6,double &dX,double &dY,double &dZ,double &dRx,double &dRy,double &dRz); + +/** + * @brief: Read current coordinates of the robot arm (with joint data structure) + * @details: Get joint coordinates and user coordinates of the robot arm in current state by inputting TCP and UCS names + * @param boxID : Control box ID, 0 by default + * @param rbtID : Robot ID, 0 by default + * @param UcsName : Name of the user coordinate system + * @param TcpName : Name of the tool center point + * @param joints : Structure storing current joint coordinates + * @param dX-dRz : Current user coordinates + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Function call error code + */ +ROBOT_PRO_API int HRIF_ReadActCoord_nJ(unsigned int boxID, unsigned int rbtID, string UcsName,string TcpName,JointsData& joints,double &dX,double &dY,double &dZ,double &dRx,double &dRy,double &dRz); + + + +/** + * @brief: Clear the linkage extension axis position (set the current position of the linkage extension axis to zero point) + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ClearCoControlExpandAxisPos(unsigned int boxID, unsigned int rbtID); + +/** + * @brief: Set the current base mounting angle. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nRotation : Set base rotation angle + * @param nTilt: Set base tilt angle + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetBaseInstallingAngle(unsigned int boxID, unsigned int rbtID,int nRotation,int nTilt); + +/** + * @brief: Get the current base mounting angle + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param nRotation : Get base rotation angle + * @param nTilt: Get base tilt angle + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_GetBaseInstallingAngle(unsigned int boxID, unsigned int rbtID,int &nRotation,int &nTilt); + +/** + * @brief: Start Load Identification. + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_LoadIdentify(unsigned int boxID,unsigned int rbtID); + +/** + * @brief Get the load identification result. + * + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param status Load identification status: + * - 0: Success + * - 1: First trajectory error + * - 2: Second trajectory error + * - 3: Third trajectory error + * - 4: Load identification in progress + * - 5: Unexpected stop + * - 6: Unexpected exit + * - 7: Initialization, load identification not started + * @param progress: Progress status (0~100)(Current load identification progress) + * + * @param mass : Identified load mass (unit: kg). + * @param cx :Load center of mass X coordinate. + * @param cy :Load center of mass Y coordinate. + * @param cz : Load center of mass Z coordinate. + */ +ROBOT_PRO_API int HRIF_GetLoadIdentifyResult(unsigned int boxID, unsigned int rbtID,int &status,int &progress,double &mass,double &cx,double &cy,double &cz); + +/** + * @brief Get calibration parameters + * @param boxID : Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dCalib_Fx Get calibration force in x-direction + * @param dCalib_Fy Get calibration force in y-direction + * @param dCalib_Fz Get calibration force in z-direction + * @param dCalib_Mx Get calibration moment in x-direction + * @param dCalib_My Get calibration moment in y-direction + * @param dCalib_Mz Get calibration moment in z-direction + * @param dCalib_G Get gravity + * @param dCalib_X Get center of gravity offset in x-direction + * @param dCalib_Y Get center of gravity offset in y-direction + * @param dCalib_Z Get center of gravity offset in z-direction + * @param dRotateAngle Get current base mounting Installation angle + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call +*/ +ROBOT_PRO_API int HRIF_GetLastCalibParams(unsigned int boxID, unsigned int rbtID, double &dCalib_Fx, + double &dCalib_Fy, + double &dCalib_Fz, + double &dCalib_Mx, + double &dCalib_My, + double &dCalib_Mz, + double &dCalib_G, + double &dCalib_X, + double &dCalib_Y, + double &dCalib_Z, + double &dRotateAngle); + +/** + * @brief Set calibration parameters + * @param boxID :Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dCalib_Fx Set calibration force in x-direction + * @param dCalib_Fy Set calibration force in y-direction + * @param dCalib_Fz Set calibration force in z-direction + * @param dCalib_Mx Set calibration moment in x-direction + * @param dCalib_My Set calibration moment in y-direction + * @param dCalib_Mz Set calibration moment in z-direction + * @param dCalib_G Set gravity + * @param dCalib_X Set center of gravity offset in x-direction + * @param dCalib_Y Set center of gravity offset in y-direction + * @param dCalib_Z Set center of gravity offset in z-direction + * @param dRotateAngle Set current base mounting Installation angle + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetInitializeForceSensor(unsigned int boxID, unsigned int rbtID,double dCalib_Fx, + double dCalib_Fy, + double dCalib_Fz, + double dCalib_Mx, + double dCalib_My, + double dCalib_Mz, + double dCalib_G, + double dCalib_X, + double dCalib_Y, + double dCalib_Z, + double dRotateAngle); + + +/** + * @brief Set the calibration data for the force sensor. + * + * @param boxID :Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param pointnum: Number of calibration points + * + * @param param Point data for force control calibration. Total number of parameters must be a multiple of 12 and between 96 and 192. + * The parameters are stored in the order of X, Y, Z, Rx, Ry, Rz, Fx, Fy, Fz, Tx, Ty, Tz. + * Each group contains 12 parameters, supporting 8~16 groups (i.e., 96~192 parameters). + * The first six are position coordinates, and the last six are force control data (unit: mm, N). + */ +ROBOT_PRO_API int HRIF_SetFTCalibration(unsigned int boxID, unsigned int rbtID, int pointnum,vector> param); + +/** + * @brief Read force data from the force sensor. + * @param boxID :Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param dX : Force data in the X direction + * @param dY Force data in the Y direction + * @param dZ :Force data in the Z direction + * @param dRX :Force data in the RX direction + * @param dRY: Force data in the RY direction + * @param dRZ :Force data in the RZ direction + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_ReadForceData(unsigned int boxID, unsigned int rbtID,double &dX, double &dy, double &dZ, double &dRX, double &dRY, double &dRZ); + +/** + * @brief Read force data from the force sensor. + * @param boxID :Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param weaveType : Weaving type ( 0 = sine weaving; other types may be added later). + * @param frequency Oscillation frequency (double, unit: Hz). + * @param amplitude :Oscillation amplitude (double, unit: mm + * @param weaveFrameType :Weaving plane selection (int, 0: Plane formed by the tool coordinate system’s Z-axis and welding path direction; 1: Plane formed by the tool coordinate system’s Z-axis and Y-axis). + * @param rightDwell: Right dwell time (double, unit: seconds). + * @param leftDwell :Left dwell time (double, unit: seconds). + * @param centreDwell :Centre dwell time (double, unit: seconds). + * @param LPatternAngle : L-pattern angle (double, unit: degrees; currently unsupported, set to 0 for now). + * @param elevation :Elevation angle (double, unit: degrees). + * @param azimuth :Azimuth angle (double, unit: degrees). + * @param centreRise: Center rise (double, unit: mm; currently should be set to 0, support will be added later). + * @param radius :Radius (double, unit: mm).. + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_SetWeaveParams(unsigned int boxID, unsigned int rbtID, int weaveType, double frequency, double amplitude, int weaveFrameType, double rightDwell, double leftDwell, double centreDwell, double LPatternAngle, + double elevation, double azimuth, double centreRise, double radius); + +/** + * @brief Start or Stop Weave Welding Operation. + * @param boxID :Control box ID, 0 as default + * @param rbtID : Robot ID, 0 as default + * @param startWeave : Weaving Welding Enable Flag (1 = ON, 0 = OFF) + * @return nRet>0 : Error code of function call + */ +ROBOT_PRO_API int HRIF_StartWeaveWeld(unsigned int boxID, unsigned int rbtID,int startWeave); + + +/** + * @brief: SDK Automated Test Print Interface Functions + * @param boxID : Control box ID, 0 as default + * @param bState : Set the status to false to disable printing, or to true to enable printing + * + * @return : nRet=0 : Function call succeeded + * nRet>0 : Error code of function call + */ +ROBOT_PRO_API int SetAutoTestState(unsigned int boxID, bool bState); + +#ifdef __cplusplus +} +#endif +#endif // !_HR_PRO_H_ \ No newline at end of file diff --git a/dependency/x86/third_party/huayan_arm/v1.0/lib/libHR_Pro.so b/dependency/x86/third_party/huayan_arm/v1.0/lib/libHR_Pro.so new file mode 100644 index 0000000000000000000000000000000000000000..b5bf1af5ff6eef817226fa5f763202f957071115 GIT binary patch literal 1856856 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

j5eqJL?Y{GS2IO?6c*DWcSRJa`#iG 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

rx-2|7QFt<|%V! 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

p>O0hhN3^L&JQK~I|Q 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�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 Kec*x$HH&*Ps0O(L z?`&0Ry!A3y*cKC?Kpx9@I-}| z5kPw|m0-PB398>F(S z3cdJ|y61xG;usP$twijRDxDwLz6;M3tEGykh14%#Bylqk1(shrKU4BAn>p*h40n2tK9EPnVH;049#&%!D~&k5pB^Y6QXy z@Vx^gN>dP!mh)^=DN=h7K7@qb(kjI>P=30M)cEV6l;TwSZ?U0N?hi==t1*lyWew5M z(5zA^@;62_{U};NDXF8$p*WS+7aL0DhOw#eEr1cFJE(LI{!}VO+9&zc7T5=_G|5Nt z%1@V(`Y22s#i?|**lakrurYJn(SuehT&gDk^1g$$jhR}YK&5jxXYvhLjI&~iUSOoA z!$@Ta^Bcce-? zdl8amRaEy>JOve|p{6;U#*x(UN~+JmNVjmbO!?_DQh$VrXy>TfyfDdMLG?w8LaLL) zs(DvdJ1|a8G^G+&O==IAa;g)5sEY%rL0vw<;3w!%R7m{{=0bf>kx5cYjOlv{;hm~{ zDKSi;Q+i;5>5Ecg*cUmaI~SyU^&tAD>M{1|SCDCAccV>sGiIi*f#u_2TU9m{{Dk1o z!btVw0Kn`c$Y->!!b?KNVx{KTh===aQrp19QJmH953x~1+{9RH@o0$hLl*&Toj);e zkNYgS!_ilY-in`5E(W8Mg@@}YsiiQIkGpIIO614?uYr1Ue3Ym~eX{CF{S~JC=!R7D zdB5oS$I%UYVHw>da-T!BB{*WjhJco2Lm;&+OoXQKXh2i24WeUzNlznw-iba=CGr!_ z5@1(ZE#aC<>V%LMuU9QZFrWU*r0HQV#G}Af+(OrQ$vI3>_u?QGQEr^SGVCjCT&=HG z3BB3ZvVwG2hi^R+`r58nrSo&{U!n9h8&vq=K&nDLk;HYvJ2YERBJaOw0&1qk-{(b* z)PKSJKh<`E!&@(WrG<4G;bqA;!7kQ*bx;3 zPsr&C*AB+AgUx4Pf8FU*yBVmBPB>RdeI6!`T38w9i;WI;2-4_#1HNxyL^sDLoAjTv z`55QvbjKxw&=jmrFrs@Q^iAeyH?U+0 zRi{$$d+c?X0Q;C#-N4I5Qs=E_k-KhQ z1bw_kqsZi^%SinW=DZ4vf&FxDzCifG@Uc!X-rNC>W6rwdXYMU&dL<65;UR?wQEoWJrEHD5f2w5By*65~eYus;8a9 z``8ELjQYl2zWfPgM$MjxSCT2b<(GS~*)b*ZCe)fY;b=W8UCWauwK0q&q_>m;o*mqZ zyWC>4J?v`W{;2^^GYxo%X~5Q3Y#Q)%(_)J{oKR@L3%ME4S9+DIDu*@P4W)Zwq{8vh ziiR>lRZ57GQyTIa=JgL_OFHbSHb&uMAoY8gIBH>ydq8ZYm^(%img6CCm37CngSi+MUd1FL#oRS+o#z+s5s?lWwiGhj@oJPVoIt+pgj8>z7? zY;uh@42^hsW;0N4sDLN6AcQ||D@0YfAGQa`BVeScIIu%e&B7&czQ^EJsg!zo2ClV& zUCrv9fS# z6^uVdn@W-8#fQ{mFj5Hj%r>gic>&R1puTJI7@f*bmyw#X%Hp>fya}#AZX%M-kr!O` z0I0PU-{gstdJT+p+Vn&WzHGz1&=gI0_{AG^?|@vKRo&22P3mSCNlB=BB=R!8!=UaT zWm1mEru=jnsVBFecxiAv? zi;^!xWPQ5{U%+Q=qW&Wg3L#!_cIM1pHXD_MOhSVI3%-&UU|BqxZuVh>Y(Dx%v zntFM$AazSf+~%iB0Ug)ukXxlv=j_FJ+l7KbHdNGd`C+yTRDNJCvmew5hfy8JQ2Eq3C-%rVe$Xug z=Fic#Z-!^{2$SYLo;FfnhKVS5`(8B3zQ`1!g5teqlJ{N&|CN{pjA=8~ycem&Wl;Y; zlpG&!)Ia8_CpEM5Lh5^!SKsSA^}WOD(|4n(UIHcWTJ_+mXGb|Ni#tMHl%q5R-MW5Upm zfG(*3O}#aw^_(xHHIdsryMS?pg`*M5PnVH80!E%6TvL}~E7#SoG#xe4!Ba$Xtcq+; z#j6mnIB4>E`#SRaQ(MjtK@#i8`%lO_vW~m}rOU|~s924-y_mz~H0~w(VVEiXA}=iW zHb!-TIj`P3UgCe1{A~5J#ub6Vgs*?BYBQK+j79| z26#)JW3txDhx_K6LVfmz-r~M^6u+qrN939X)}yN9IHnj5xvOEkF-7Gz zI5TxE_2yIO&xJUV3-+0zrcA zsnvrnP=30M)Nf(pC@FfD*N5YG_~P%hdN@`v^(E2Vv6xSZUt8k_D?nbbI>WsWsa;{> zs0BLX6g+GKRljKo$2jJ z#vAC{+pzk2VLgm+jOyR*AP{p-yo3EH821e^QNv~(`ROuJo1ub5L~R*^#zXRl7+zTG zlrCI=!4RT)LLPdIiWwaJY@d2t7o^CH$uBW1GpF<(@+T_a>6mTR>_ax-UdK%3`AK)$ zD1 zQIDw%M<*c03JOst3L&<w z*dyZxi;arF8)c8f^UPpV5yFo#YFhvPi=zzf{l808-y++bvH~LwJ^Dp#m1}0(qt@p!L!-Qgx`HhRb@80*=Y2T zlRl*K^>P`vd0x#{o)_buhcCh_yw!xlke@Egf)>;JB-qg-D&4*`d_51w`u-+A9`oR& zE)NB4=11%Rz9eE{0|WSy7!ELPw~4-KSi}hXjZ|EC3nj~}`Xo<1sgIXej}i85z<&(t zwH5HBes?}TtpOgS#TJX#IQi+arW>R!o8cjnjG^Aw<)8Im+mR+lvpn?e%%vvv13VQQ zq2m8J=go{}vqR%mGn#E_y~SoU+sb;2>J;Nst%TNcTn^^dRXWeacpd@#XoC&+x1=70 zk*2tDj+C}aAy7Ne*G<8tH9WnF5AuL~bCdS)P~@tW z@9fEsgsiuQ=N&jv=g{vUA9ko210Y%csq}*NVkpTwJ{E!3%<2pGRHUwkk@#$o_@I&{ zb;LqAsGa(mZ2sO0pVSi}Jcq%y8X*CnM32fdk0_MjH2S$0!YRcg?IJ7#yI(|42A?kp zRiiG0<1ExvxyQE5Taa~VnRp$jw!^0m!~U(0DNppm^27WWsISAsQ3bMIRV`pRAHnxEj3^u; z^@6As*Wrkn%}O1xdM@_#kXj8Uj*`@q$eZVjK$+Crgn5Y$g-Pmq7^%SYoHxF3N|!Ad z?rR$O7g#dD;J{O1B#A+AbD4%?vn<$Q6+dPpk#cY)o}sM++|9xvgi`4**1}yc&~mwj zhU);SufRw~ho<8bch<)1Kd75~nK(b{rH|A`_eNAVM!{5~=K^L1Ne08He}@A6Y!E#~ zO6(j&=y2dRjD5LmoX(5BdqK%UtG=$Mp49On_2p*#mWOBj;*pva0JXN_;TbfS!mhjNLdhS7u@3SIF%ZeA5^X_iR9ISGIP6 zl8!n4Hf=iQ!jwfG(JF_KUG_ud-OFy2Q zv%2Bsf+x0Cvw?)4(+eaJOh6HCUoIFh8(RAGR8&}aqO4fBl9T!fO!;D&fY$#esCRdd zlHwR=N(C>cq$X@Bhv(D^Uib>^EN5}SJ}cG3N|_hHj+Q?j16Mg{vL@8DfRPS_A5?^U zqu?t=?q)Id2O~G0l3UG+hNt)1J`mCEdy|Bw|9`by z|8IRG?QA2K)6b%-ErGmiwT26v)Cn*WAv@5*ViTSlzL_8|hmnjRE1a{b6zR4dXP?48 z(9INYv6o6xABTyf3dvcyZ}BDg-h}ZKs#KzGZR%5}VV7Aw;b#V^zOB$hN$M%ufrPmI zd`Tc?z=$S3H6o%$fnG&E)eH8*3Mxq*2opyYQdn=czydWqy)3qsmkd&Qh_H7VuVfMj z8)!&UU2%19=)E-H4)2C$m4sZl*lG&r^1IL?MaF}PZL^|gi2$$pJ`GQOD^}f$0IB{5 zZQnh$bQ!6O zL*gbl^JawGnT>d%O62v@4*)dUiib;!)V(2b8@fvWs~tWk2ep&Mr+dMX`fCVpIzPr1 zL*z7vLu3w9(|P+_wQV>l$BH-b#7V6OBZU&0X-*u2kwQ06@9ttUXTFCgb#w?H4sI5& z`{F%N46bT#44r*bfm#G3wTmOZh}uOB)$p|4279axBiu)mx(6nXDx~6fr~=2c@VyEn z3i-W{$|tJFy*~8|>>*Z9xN9QyFPJz=ik_@wI06qLBVa@izwL>L7~viAe5y0}?X1FZ z*^t@`CXOmZ;Vxr^nY3tQ;ekbJtA}k`4qVHD!+~ij z{qc#kljbACDgAU>Gwu_0YRcY0r}XHwW><_=PA%(wds+&2dphaX|I)OA-)t~fI3{_` zCMc)AWxqD9AgR3x{$k5cso4(l4K3i%aRGkq& z+4HkN_25=EC(1Vgq-_O8{8cNi%Shb~BPGUEvY%rjqC_dymg{`LG(`Ehy$vJHf`eAa zu22+^1iueeLh@AgD4Buaj_PAjIzHb7hs{Ou(`BUg-BB*MqB`6L3yIaDqDvjgv!k*# zkAtaZqh}zEZ_0*Ulv=Y%vhoscp1n;}nTcUb! zf1|Tp&p;G9<(bIg(>NPBQcp!j&MAFqo(X=XCIcltFXa?=uQ@k|{PR;ruYiA*1mTn( zo0qb+H~hC)|J(Bl#)NWFMx`B*QgGaq|VA;OyAl-{?%#JW)^)(cXeXY0$de9UatN`$|O_RVJHD@+-+ z72c+L)l7=-U7*rh7h{3Zb_7@1{MTj65iTCh=UoUOPj_4!VCy-&_}n1mEmT$Iy0UE$ zeNDs%hQu+?P7&#blFeL=e6v7eXr7&JmCA6puflFfG@b7{nH(wfVnu2#7+EWzfK1`j z5ywzb_2c%4Ju>cTvAJk4s1pVtGXjVO&oYb^t>C((`dgvJ<1{!C%tK z#H5>7Hl!Ya@oE(Mgl5-_o35OIWWBF+Y{3>Qg;ARP*pVelK~cZz%q_sq-ycKE*_KBC zFfV4LChab#zoHITQeU``<{!#w{vlHHPoC!XC<5pG|3ZFSw#kjP zUT%=e696M}dwk~r5Ys>e2#9`SwC#ST%Oxrf@6tyW{1)z}jiY_!ROBGfPL$#=T7 zM*v-#n4q(~ph;bGe$WLYEbf%aDn*4wR$;-7mU+lBJ50xJF{fv_hz_t31*9`~q&sb% zL?EAJnc%OmlDdr4uOfp-^J(m0Dcose^bP3HJr=$^W! zh4HEf@^OCS53n8Dsd138DBU@KL2*3l0@72DDxK2T<}X+XN(Y5a*G>k%fk7Y7VR2F7 zHRxB8d?TRn8W`!cI6oUP7-L{9uupp(>?dunQrk-$sheTqsDe$sh=*`MtK(qpoi2YFmaR=J?(11aT|OaU_=jJ5mYKs1rPhw zA=pI~^pJW4CXSNSbDy446|VbzzW;#o3ykRESGf_1q{zu$xCa>%giR_aCABV0992l< zPE>)T1xWcYoO`10N>Wb|kH~PUsX6f93?rI2AdH9_ z1ynVk+6#NLt*Llflbjvko7v5$&ceRIY6@2}QezH4 z6D6rBy#UX(#-JRq2`TVGB{dgD+Q$++nwrsBymTeWvN2NJ1NF`@-7e1UzU(B$qVULd3%g^8mQL?~{h*iij7 zReu8CaTrgvN+tRsjHwbZD}7M+P_x!!sM zc9W5}$BHF+VvWpV0$1{;0%#PB#OFt^;Il8)812Dbr|-T)tEjK;a#Wnssu(lTq2IC| z=*6^q#>FuUzL*=nf-fp&)+6w_G1Fkhv;R(9^ft!D^#{S(LHo}FoA^^MC>iKZRAPJN zSd9kL$n-_Oa~WjlPCRfg$PKgUe0T~DiqBjRXZ&*ft=Cb}6Q)d8@uVbfssm=DJUaix zVd6Ei4#L^NjXv}|G|`>-JPt{y(L^G0CLK!K3-zgA;k;O=DhYsF83I1oKu4d{m2L3pbQS+h&bl!`Y z)~wF)SmI$ssb<>Cbbi_wqsn89NBqA;4T7y5gKSob+YoGeOe~!7H_*QX*;pr)l{m2j zGOf-<7{{r^N02*psxxB}&maTpr0s^Y`q93Qs$BO{ocO1bZhXv9bqCO?l77c2eRT)Y z?c#i+r>M3#8>B&WR{wE~qcZDWi&qVm^e0YmuRDu*tde*ve%;y3xWuVU?-A&rH z9`5T%k4t)J0^GNgUODN{Zg8(>IaWzpc|7E8N^LPXbaLg~3|AZbI!8fU`TZ+K4|I5^tyyw?njE5f!2m zJdH|FT}}(B|5h9t%&f}w-#tgYJT0XDdx;zVChj(Yr$rX;Ajs6>$;;v$1ereZ6lU=bg3KuK6lL)ag3M^~ z6ld`cf=s`7hGg*$f=pLm>c?Dv7VjX)4C*29jL+g71etM~5uTdGI|wo>i)VTk?;yxb z(uk{?mBl*IHmOU*`lAgcj7v&>c!w`a2W z-Ed}(o({kvmB>2?8pW_E#=DK>;)$gwidj*O6Q{r~=EW0@lh|a6TaJV?c|5C_w6Mkr zrlcM&Os>x7XmB>>%vufQ6 z&jx+)6-T`WSGSMwMdfN1{$eUkY$C}M3jCxxCy}vI0AHQC!bf6mIReTu2Z@J-}laie+7)wBL-LQk*hm9NSA@!{`=ip-3{W7@{j=l ztDE$CsChJ3t0zNTM=Fy_KDZYeOGdu|ZBxy8P|n@Q0qXb2vsmy$m*{MYaZwxJc^A0N zmqT0exO6!0B~>Mld>U{1LuOGto|H{mGxFn-;noY1uFM1buAb1Rl3H#7ql_STvt|&L z6O-41U0m-?DB|_H&fu;Ti!a81GOt>4E?-2=$-EWJ8M;c`y!g%;Hd)*qFz$1PcY?f5 ze)4b7M^3>*P$zHM5BI1TEG+2c?R1ZhqdW0&VkVVBeaf*f;C$e*H??{T-cMkhiJ^Q| zDL35>|3kIw;Tl(S}Sa zx6Ffk4rw(TI(viB-;+%FBOACEXTF3_AVIu3B&8+LlttelgI?VQ>SCB%%5&?!w-Vep z|6PK^e*VA?A8@x5VWKqfIxydBfa=ZoJ`cuQp7=s0+JumX`FZDqXf&#?!Tvr3Q7QSE zkpJx9EUnIg7!2CvSg}fF;Qr1FpK5~Bv>aFS@vT}cqJqkLD$2Mc`28+`$vA{I>LlxX z24cY$ahd?kRUw=*+!Wok4nCX)muJG?Os*s_LR!qwM;Oq-=C6=0PU$q$5 z4Y(Y=Wfe9Af&WQ)3`GqvsFdo5rZ zc{m9(7oVpnr?37GBG3aYdB{m9Ci`;VIsyI=osxnc;zIoQ7lS`a-+7x>H+g)Cs^o3% zJ$E4r({*fXoN(^(moO>(!}+r0&(bbR?`4nACZPFe`SWxWj3&SJ_f`JxBnccb3$IELQPmr0C7PjaY58+1C(ZTiI@@G=KhcNo*qZbdPw7Qf
H&Z0{La$QBVN_e(9G~TBlcpmZC0l?l6NOcz0 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 z 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%0A<5n094ozZ@RK9PHMVv!2JxA;W< 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%0D5O2fvW>fjb6rCC&; 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 IRDSIIKMlT+Z~y=R literal 0 HcmV?d00001 diff --git a/protos/cmvr/config/arm_config/arm_config.proto b/protos/cmvr/config/arm_config/arm_config.proto index f69fae94..de6b4079 100644 --- a/protos/cmvr/config/arm_config/arm_config.proto +++ b/protos/cmvr/config/arm_config/arm_config.proto @@ -30,6 +30,7 @@ message MotorRobotArmBackendConfig { enum VendorRobotArmBrand { VENDOR_ROBOT_ARM_BRAND_UNKNOWN = 0; VENDOR_ROBOT_ARM_BRAND_AUBO_ARM = 1; + VENDOR_ROBOT_ARM_BRAND_HUAYAN_ARM = 2; } message VendorRobotArmBackendConfig { diff --git a/request.txt b/request.txt index 9a4d60e2..8642cc9a 100644 --- a/request.txt +++ b/request.txt @@ -33,5 +33,6 @@ third_party/modbus/3.1.11 third_party/visp/3.7.0 third_party/mainif/0.0.5 third_party/matplotplusplus/1.2.0 +third_party/huayan_robot/v1.0

{3lORijLp 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�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%0DBnA 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%0A_)^2C zqd(tl_%|re^@e|o`RAjCH$gc(Yj`Z?lW!P)A^69JFGauJXZSa$$3G13?yD(?$9|hv zbCvdkeRabRBHqOCk5SI;4X=xG8)|^*7m=6p!T=F&7@K2Dhs|}xw z{nQG>Hz9tH;SZvmA2+}{n1y3FM^-H8=elHh;ov0=I6~hNPiXH z4(;t2V}Ct#jyL=>q$l+tIv+y+Ok-b(4Je#zcwg9`Z}=_f*9#2)0^`FChG#+lPQ%xt zJ-lG}@0f?YXZQ@1|JR1cBi%m?Uy62B0rL(ihq3Tqo{tdzJM!Dc*ss9&+}&_~exE~s z!{vF2;f7z0bkhw_LOJ9bo{apSZ}?iw8|NARF3Mq<;VrzI!D_=tV*hfF;VqG0InI#s z8G&@4Gxpb^9Nsj1H2nG4@DIs!?mpGne~S6TnTCH2KH2aez-68*`Uk-082b@uCvqNF?5m@{uQ2x2 zQT}%s-U|7W<3G{)3H|X|WB(E6y>A-+D0DtC{4R`3KNwyO_VVf>Nw*v7I~o28--dFi zYj|_yx0T_mT(6_+wub+T{Z~K3FGYN~;kUqkyx~>QuJR4gm-?>GD+UoF9A!{s=xBg#X{a~<7;^aWcW{LKM|OZNI4gv-KH3x zg?g%Q_(JrD)`m;{b~k({=JRQW---4x((qK|H^cB8*e@}BA&yg48h#(z&s~OpiTT4D zhRb=vj}4do#P^0jhJJL=@X8pMl2EQv&JSb!tZVpl@TZ00V=*3fFg6=U zBhjzVHheWZMj^-W<(LngZ}>Lk_bS6jp}uY~{5U1<<`yc=_rRjhOa@r$n_j) zC*psOv6t(e=Nm5XEuC+;yp#A^!&hSezSi&!sNW5S55)NYl;L&J@8rHb@qat|-FwDf z^7W~BQ7y5I0Z)XNiw%XRu4hTnsB^^W0k{opghe?hwY4R4Be z5{q$2{I7!jNL9n-x=tg*_sV|U@Kq?!frd+Yo?-aA7~duv&hN+Lu+H$c(BEKqeU#4@ z!56)fLpceT^S12_m*e7-4DXNpo@}_B_bf1cD(Y{s z;pb!gyvgt{Fb;lVcvH0hN6{|Cf632w!@Hs!-Z%UrjE7$uF4t-PG+f$4UDTVTdnMXW zd&8$-zIUSGoiJXVZuokPFXtNmDe^nr@CB&H%M6b|x^jPw__-0~xX##%{sV^Jiu&DR zxLiMd$#A)Ty3266uPcP(a`ER`ly8#ZT~RL`4WEy8GRW}Lu-}|z_;nbU?lgP@>_0L5 zLG-T{^?ZjCKO10vc)H=QpdJeh-w*qFhJT8Fy3z0v=oc><{yO+ChF8b<&(l zJD2x72+zSdnQ!d-p_~^Pz8m%OlHqb6)hC9pMZ5aW@Ve{(g%CfiLP7j_0`0uA;e%0M zEes!ndOyzaCTRZ!hRbz}d4{*ZJoYxj7sH!V%02hP(E9{T0I zhd&y7xu4@N#kuJqTR}vaA{4mXn4=nl*f2#XNO@k16_vvd7u&b?(V)8F&A{t`i=Q|3 z^D9nN_T1DuqCck@``NHR+t^FK&QUyubOzHuhupCHD)#df9}T8}i$4pX|1V|F{=}f2 zTxaa%{gw9`d-3N{#o3=6`seUu*nJiIlj)g!|0S?6u~=kQtBeHH7x;G@AWhS%YOqVRjzeHGhR15a#Nn)e558@sW|J% z{UP%H85tiQh5Zf6p7rH^`dh$5#5k*u_n{98>y%;qQTe03IS+_OnJ``k?Sd89cg?UoYkG3v}WQ{~5fp;dQa!tZsO1@CJsr z0&i@13-A`;;(uNE-_!83z)w*+Ty8J(h7`_JoXaf@`ez&74?IJ0t&^iT>x_i`d4>-K zp9L=dNB3iI=zN2+XZ_vq^BTojKLkgvUpbG|l1{|mz(1K(%#rG6_nrj03xKg&?>-N0o(-WBtQ-o{?u z<9vp(m;G9%;_T-x_>*P$Yv8$x^LQfrwHb=DPE0g?P`JQwc}R4Q;;hr03y;D=#aX8- zbQT-l1bmg@TfuKJd?WYJcPh?0uVA9K zOK~<`f^zu4@C(5A8J>dq%#Viu0sF`%BuYWXx2dp?H9Q-h+R}J3>{vNoL&#qKgJi({Ro~!B()cbzLxxRjf&ToeQ z3Le?i>q+~M$MH_A;j!Q;hSvhGYIqg!dWN42-q7$g@K%P81aE8jaPV&6Qa+V?`~37O zgO4>j<9C_w`Bn zj5*1d&t}*QZ-;*Ow&7F2t2Oi6iM{mGHik?4>}&Xxo__nY41W~-dc&La^7c;~z83s_ z!@mZv*Sxf!(jK}P-n@^u8)x|C;Pb(`U+{RiKEm5CHC*NiHyi!{&KGYmTUIZn*51tG4j@kbJ!w?c)u=rGE2I^t`LFmvnn79z&+`d_iBs zSEC)CtT>maTn8MXIP1Iw`_YC!4?b3L){*1=EX7&pQ`pP<7-f9h174u)S?3`3zZV-W z_ZeQPIP1sayzwH#4?=&L;;jD@_&tiVPFv_~FuXPRR>MyM-)?wU@E5?P9-l%z{$lt( z@U|_<2?}Cg7vsYT;L=W>LObbY?DJ3X+mAN((oQlI=ltfw&&h^Q2G3DEp6sQaOjn$B zu7Uk5!xw>HtT^km#rcyf6=$6duwP{OJ>bg}XB}w|HybYP=Q+h$e>-$uH2i7s_YHps z{6oWE2LBXX>T4~^KfV_C@ZS_?9cd5I$54bq0{yej zNZ7|4J`_Azan_OXwwB_ob1v-b89oWTk>ac)<5(NTS!XuvI~YC-ysP4@BkgdU;;eHm z>@y5s44$Pp>&X77P;u6I5cbmzzZ-m>;h%$FX}H|qyjXEgw_O~4P`FWXPInNl2dy%^ zH~3m`Y5xyPJ2X7ucqbpkxAxy+KLkA0@YUd>4SxiDuHhemuQa>@`s161R|o$DoZBIn zgWT`F&)CcFc*V5w{)@fzuS$w@xm}F%sb=^L@Y;%Vy3)UzD$Y9VVBga4o59;E&N|Y+ zj#Hd<)=xjzZ>(nixuZ| z4?_Pk!~X=o#qf%lSKMZJEchnFPXd3;@Ydj46z6QP_ z?B#dzvW>m;qiKqBd47pkNebM#Iy!(V{TSBkTajOY6m zXPtep|IP5Pzz-_UIx=5M=s*z)Tt2K5h4b^thRgfzswmDn(w^%p&N{WA)5!2@;LR0h z9oa9Ora0@ghy4h{TY;aUIP1uKDN}LQITiL|t;kslTMd_a^KXWyVqVpuqu)++t_DBZ@H@b#8U8Z(BE$ax-)?ve z`qvxaQVzdxB~aL7>}TV+_ZMR?{VSrA-%i?LZQKtNV|ZooM8!Et>0i|q=Xy_teQm?L zgEvr|b)bJ{mfm6lWdj7bhvsI+ww|m*E$K_g9>Cq+gtFxXhO(87}js z0>wGq+6KQfLG&^?NDmeYD|n-C~l_xwE3zDKPf(yXo_dz0}uI#o2#(uiA3M--7=)DX#15 zHpN*-_EUEmz8^Y|87|NHY&N`Fb)T=DhW7%0-SCd!?l4M<&+B0Sx#7#ezfoM* z*B{_gU(1_$KRb2v-@UTQ0^t;ci`0Xz-_F= zUz%;~rGBR=&i)UCpXVFi4}7-b?7xhw3lwLab76m#;hEq|6=xl(-<689&OF$!G5k{S z+Z5OJvO#gySq1xzhF=fd}JJZLvhxb1p9XkKOKCx;;i!)_!o+^&Ia&r48I-x2gO-Oo}2zt zan{)j`w;d6Tn^0N2ai!amiPp&APO}VXZuFA9S-UmUKPBd;_Od6_NT2CXPpVKZ*TZ$ z@Ggq8Ke_NneospLUkH2I?+Kp|eyY)#0iCgmvp=g~Ki+V84tJ8`?9Zjpk^3E_T|HTu zR#KP^dp6@+h5p2SRBH@h1^$%bHJh;t9rvkpxjegY;uLln`#tqN|H0TxJO5L0&hL}R zZwS}(rQGfTk5Qb{m3Cf9an?zy)a3f(+pn^K2mYkiNiRVt~l$w0{cwEw}PLeIP1v#tw?d!saKai zD9kXtBKUm6=YcOYd@T6YigUX1+|W&mbGpaZ<5cPSD#P1=->Nw49D{s4WVk#Z^o-&6 z;=03|hUa7a`A~88XIee>hQ=+!bHKk;oc)PJz5J{=>)ZsF#aZV!*iSY50QmXfGEVluefc*SF88}X zVfaUl*h{*P0G#WQv(yIpeOqz%Cmzp#zGrwW_+G=SfPZ0lCGZ~%ZvejE@Ot3CE3W-a z>Osy?kaCcG)c}`#$?q^VRh;wH68bF-Zw}tk@Gjt84etcr!|+t_-iDt9J^)<&{|)sr z((sO`->HVL2cHct`AtJTE>fKHdvXIVtsq!xcpCUkhK~ln+3?fB?=XBa_649;IF)Q@OPL zc}2D#hI*-#%CzKs<9*Ptmg;cvdB}Uf>l^+!cq79f1wY2{XTjSU{tS2*aPj{cw5wi* z{{=qQ@LQoX%ka0sml`h5nQt;YF&wzC)9?o1-x@v^Jf>&hg7`lXyuRV5HS~5}4VULp ziwyr9_6rUF4g5aC<+;kw4L=qAx1!H=aw*2 zx{urN)nSK&BE?xk=FhVgXPs@Zztr$2z~?K@Ix>G=syOR>3j5`TzYBhw;qB4>?=rk9 z_XQgTiBqyK;d2X2X|*Kdm_H$b9b&!)3m=M{(Ad=d?dD{AK8W0UjdT2>L%3 z^T1dUl=|bJk;0giT}Ca zO%0y|exl)Pz%vYg7W@XoUkAS(T*d*pzVm>wUxaaEhq0G&<4wi69_4xIcMZR0*_Ukb>w&F>UvozGzZli?qN|Ef6a$a~$R`cs60l)pT89cTET&`DC9b!2~9 zQ*ri3p0BQJc&#R!8trctXC2uewJ}`wcgGnn`<)?*bGrSYKiu$M;G-4iblc&)dWPbx za}n$(8$Ja*M{(AX-;bECIO{ZMN*@$v8D0ncVsPn?BXBBf7$S4^wVz)Zv-Bl z=F=Db3E<7aWn7&X=j}Qg`+ITSd4RE(emz`q&Tm)bYn0*gobXwSbGga5I$3enISV@3 zhK~k6PjS|fadnpBtTPSvvkjN$gclhu&*d&Pd^vQkQ=HSCpG+SVZc&`meH`|;8GbkT zCc{@Wb27nWhRgG}TNLMX<^16bigUX6Kxe1ncZ0v7IP1uMWRK#k^D^u|F?=KV7mBma zH)x+fDb6~n&FO=}0mD0j#|`l7W&BwMUcvC0;FT2Tbme?wUBx-wQ(Ji5u;HoT?G2ab zNIM%o7xu>~9#7Jny?oF|aZdL(*ryqO3-}<#Sx3&to@Kb4zn!c&>pu>iY{MT0KTq*^ zvab_M9~5RO&N_QwKily4z%Nsrb(Ueiy-0D^No+|U6qXtu3x1vA?8*vf9R#Ztk0&Pk zk-HUV{jM$j2KO0W8~h=~X|^1DTG*4Y62rwzXj{CUOW$zJv^Zz#?>@|@v2hRglf zyA@|wHqk$a&lHcRxa{wKP@MJU`M~{#kHvl0zbnoSzgQtSa{%a1dFHAQ4 zC-4HL!})rR{yEH1oYTDr`g0Ax8~iH69|m7y_(t#>4c`jB%J41VYZcdiZUmS7O1`!j z``s>AG)^nd`I7tgUo-qgr2C%XyTJDt{toyThJOzJjp3hw{|GMrSCZ@6r+9~j*TnHc z3U~so=5qMfn+0LTIbZVJK@-D&JH~I&#_&WukI=zzdA_u};k99ZlHoPM`y1X8{1n5R zgAX_SB=Av&p8%e2_#p61!%qRvHT-n&0>ejuPg7i%^L%hyFH4MlBpXn;S#hqH3D93_ zcslrc!}GxJH#`UYQNw3~KWX@M@TbAW|Ndxidkt@o_V$zE@*e->fi8Q+*L@b?e$=ss zzmNMUiVSZEzQXV;!Cy68?k7wfR61R`AEOpH_X{3B_j|LTiLt*I_c}7u5PjRlV zD_VP@lMSB(K3H*1SN6+iD9$?TVL#UJJHRI>&N_1bGgooec?R|chHnO+ra0@!e)$r^ zPM9G8-_Q-@zFPi4+3v;Dv44MolNi(48H^X zWN@kX=Xim_2xFfLf3l6e)cZ8Wxg6r!IGNyl!=u1wE3WH(f#R%FANE%nF3+nkRh)HX z9=lR;*69YFHHNnbzfE!0xgGszqv6sXUQ(R(&w$RWhK~Y&3tZ|&eqSekFl|ghxcvTa z4REO!3?o4!WB*+xzx{E>Uh1Wf;_Rn9_nv0>c=$O;aa}K?6la}TupeXibntY=Sx5Ry zw&JX_6!!Us{|kJ!;Tym&HT-Vy`QTEXVYJ)pl|B0-^|)4X_U9$&+-|r$SHIrq_kjKf z%AWPlU@;0`8v9kKuh0;`Ug}rstAgSz@fG|@G5j;|YKn8Za(*MMIO~Mk`t+I@{wH+W zfJ?dUgrCPMd)|*P@iIX##aX`w^!piJ1$?mK?ZJl`-WGg};m3oIGhE(BkO?mSe+d6) zDtj)U`S5?Xu^$Kf)ykgpH2~?}YItw(dyGytbRI5)zhZPUp!0^|*2A&rN90q0@XpNqMFsUyZ@J-E#S?gidqAuLEzRIO|`J{Z2P<(cf9o zr#INxOTRcnaZYy&^v4>$5qyH;oUY6(ausKtUtwQh_*dZ56lWcI&&(XfS?Bn6^g&^+ z;hn%2D9$=EKekU5>e5K;7BjeZ|inGo<*snKyF8Bt;Sx3&#KcP75$a^TB zGW=@jY*(Ch!k zdELhi?+CsFT*l|QiQfKAV;`>Q`8UR1+U)_wxjb({x&2}Ijo_hSUQg1Mc3VMl)_EEB zDTd2?I>Ltk1p6k2{{Y@naow&uE6(W#?Y)294F3Zob%Nk`TD}}j^N)a?(&7_Vt!Sebtc39Ps7gu zj~MR#lKS1}qd}75tRwFYsbu&~(5bFC`*SJ_QfR0+>pTqmriO0-Z>6}d_b!UFj=WFg zIKy9t&Pj^1&Ls4s0gAIuY6tqDFv#!|z=tZ%Ix?P&H@q3zLyqFCpAVh$44(*onc}R! zE}1?kEHnJ-s-CY>ob|Ur=N7{s2ftl$)^C9G0}m+9IkE%L!4~V(e4VU*0zMGCq8)IOl5&{Qu1G;ov_T zJ{SB~!)Jm2r8t-8?qje|GJHLFWyM)%BUc)QdWy47sFTA%L&N2L zD9sdS9hskWP@HwzK&Ol0Ey4R5o(4X^aCtAuXv5{bA!iw$4W03dv!4&9_s(RU>&!KL0LpW*;;jD!be0)@FZc>@Y3Fi1>o#T2(iiw-gH4LF{sHJb zX83;arwxzn?1i2)JOuu#;q}1ZG`tr0d*I@~{Qme)hHpbZYI3?yU+mjqzBJVE4DcC- zZw0^6@a7mFo;N%d{CmUYJ>F5HygxDytcHF?aPf0hS8vzM*vmN3L2<5^t|*@_hPMUp zt~j?VnZNZ_oOQ;)et_X4!ABc@0r**l7lEH^_!{s$!*2q=!0@1pFNcc_-wpe#3||8K zC5B%GezW15!Pgpo7x)IlPei?JH2gU5XAB<+{=DG>!CzLK>*b?ppWk;C=X#k7``w1m z0{>WX){*CAzg3)d9)kT3hTjkV3%ImHd4ET>(cUlNJ&*PA6AeEDJkxM_U&cbiH^Tlw z!#@Il-|z$A(PxzQ=ag)a${(}8H!w-O8Yj{JH!wrVl0KeVvVc>TgehT=bhR*|k((tL^+Z5+~^}})W z%ZhWpalU+>GCT@= zyW+aNy{fn?AK1TXcth}A;L@&AQT_)FpAO#qEbo`tF9aWK_?_U>4BraA((qm2+YSE_ ze4pWoX#X|FmiDIscn`xnflo5LKloLKj|IO2T-xoiNj|*?jeUT5;|s=K+U;A4bG@7- z-O1Han?`lP9GFn8XgPY7F^m}Pn7de!}Gys8h$DGYQvX+ z1%@Y|gxiO|8Bz2F)++EYhs~IluQEX`V&%7apHiq8>KG1M^Pw8mGyTYFgaH*Hs+$boV zZ|tRf<|@wZ7S?3qn-*5Q$ z;7=J|wFhlP;TgkIz;`Il>9(ut%!4--=XBe^{$0ZxgMR=n<-8u{{I%h2QIEeV9X5@{ z`M2mypT6j%LZ>dcv@5Bv=8ChQx51xd442=PYp*yqvXMUUAlG zkV+pERv2C*)!|@`;;bY6alPWK(;xQt8{Px_DZ>lFpD}zQ_zuN6U3s4RO~pCgdtm>r z;dg_7pg8MD`~OmL*4YaCZw=oF{-ffovm}~6DEy^3>x}8?a1b%ke{*|gJ`_Awan^a% zn+25>XZtH)U)}II;B~-5WE(;MiE&=2h2hmX5ei+E4x{^G91i*!F70-x;nI)B8ZPy6 zj^WZyrWr2n`4YpWUKSg^6XV9shKrwf8!mo6Y`FOOwBh3CtA>l8yA2mVzcyUPv0uQY zUQ*Cbk|+7{w&PnZ#bd~11M0V);dg>JQk?5i^4msn)_Du|9SnaByr<#cgZDN33-FT_ z=X7N}8KF3*TdNm+P#A4^CGfF|vyRkDmg20F2KyYtyMY%d&N@P>{XP8o)bOvszfxS6+kVAaC#5%gL+|A>JOTWm;;bY6sM=(=S@HRnV_;v)@Rs29 z71!n8QgQa@1lYGRT;7M$5nRguz9{cUs?PgK zigSKj_HpJxH^Uo)rx~6GKG5)9;HN1bPtuaFF^Y4#nXn&c_}So@;F7OF$X9{kE5K(f zop{o(iTV6x#{N4Nq;R9Lm-4w)arX0S_;ZKhSAgH6IJdXDEKcDm#aU+)?4L3Ge()WN zvrcmsp|Df&c#6w7yi0M`-vyly41Wdu6L2Zdc$C}shSvrU&f(Ncu0u5gPcpn6cw=yB zpWABr?b{gp&5@q>G4@jarz+0*s^8b!4>i0x`00wrlj(Oj@13qV>-2+trr{@npQAYI zw65*bEmWL!vSB~n@QL6TD9$>Sk>AS|XPt$xUtsuU;ENPz9l3w+dc|4iLD;V_{BH0y zinGo|I6v^X;;i!??6(;HD)=+t(th?KU%L&Dj3O}#p>y#~U))5hfHyI`DR>9NM}cP= zemVGb@C4H4eC@?}KF8QEf}htJdueZL6z6h~_w1}Qyh=Y>N#QQVIbCTd4=K(%yH5ja*%vgH2h2)r`IvOZ?xaOx#1UZCMb+D z{E;f2k2hS>%{5%oooRRk4{{V18GbhMy9`{)CzTf{+-2+~-G>xs*Uy#mH++JWzv5h; z;^%XUv(8npf6?#-;IAsqI^XjLh4&O^owcyvWBASBdlhGiM^adYeh*M_)_D~6KN`Lf z{D9)DBlR1R!)hhh`{X@GF^0bgo%-NX{zWMN7RsKhu!2*i<7ma%PkH}Qcf&u1es9CS z2k&qAKJX!i{{=qW@ZZ4C02lwSg8!2Ye-`{Q!@mY!qV%~uKf}E1CSxCl{R75c+UHY- zrzHAv)YZSYZwbGo-Z|qr%!`sHb5$2_z8eShdKNvnH$?N=O_$75b zPt5b*GX9@~_E5`k(Qj(_jnMC4xaju+mwLGu^S!f_J(qu9lz+P6J;2W~d^~ur;bXz4 z89o#Ie8UUDFE#vj@Og%>1YZm;`D%sz&JD^wmhy$$zJfay=luQwoqG)b4SbW~@oB!E z9y2@^e2e0oE@Xn8;NoXH`1yh1zoTBhH+&pBOQAx(|CaHg2GUJ2yb^dV!#ji5GrT=` zGs6dhw=%pRcsFp#*ER67m*I=R&oDZZpflF+Oz>>O<-KG1hUdY)NO3OzNxry(3l(Qq z&WHUR!)JgmP+aR=r#S0e4*Q!7zXW_WxRlQ$D2ICtkK;rrY%@Fz{(;ed8u}j_{v`O< zhJOV9o#DH|e>MC!@IMWg_lqT-=lzv@jf9`o3?BmC23*>0wE=$n4u)3&?_qcY@ZN^k z15Z<&lPvJ^!D)(f(YJ*C2*aC$k5ydjWGl`(U0|PYcn9zza4DaeD2F+QKL>uT;rF)n z8Mxo*7eW6a!*jr&H2fy;t%ffHf5Gr=;5!Z93?7Sum3-}lpOwnsZNR18--b>H!`}q& zZunmClMMeDyuab{KCe>@{}%Qmz{UTL?R+_(t?aohR1_XGrTMK z`G&Uzztr%vz~>o08hoMR?C15I5enBR&VJ^?{(8f+!Pgi*6MUWF)4}gGT;ALDfZ=ms z|G44Tf^RW=8Tj*tuK|C_@Ri_i8U7UbF2nB!|HSaBr?_GZJ~v$6hxNVTtziGN;qpGL zzYOmK`-mw%odo*gdg%q82rlh#2ijq^GI$50b1HPY7(NjEB*RC8_cHu+@KX$*3_jTK ziQuEb#s8Y^eLg3Y!7nm8bD?vI;g^D6Y4~FBMTTDmex2c~z;7~q1^60p@n4?bf5z}T zQ7_*pop{Qs%+J3!e07{Jhy8{}SMvO~GWbEm<@h49&~L}>htvHS=gr0;AIuqTLi<~8 z_?_U~_Be>WkI{87EJ!&BZ-fQ5=l_9-H!~cEGeIlE|Ajbq4^D%1?gQ^=?B7Kkk0lh_ z%X`mo`)M(6!VXiwZ9~O;5ICkU#k>%Fkl(DBUkEH`@G#;9FcL1;*Ph35m~gqS@iER%2$%PL zwZd_>aC!gR?^S$4!sT}~EPz~K=*WEq(l~_6{VD?)P!KN1*UJr;>~zWQy3%kwEM(7r@Ro_i{^ z90SQ_!{xcu3W+{{q9f0Vk1||-mwA)nGVXn6xIE`_LWR=$^1JHO4VQ890n2f}{db0k zF+sQs<94zCeSCTc44>W4^9sqO{gH7vXgg&_PGS0Z`YtT+-;-sjATv9?ZDHp0!k}$N zdSQCdcKnnnL0d-)@;q^ojLh*xlg`e{otQ_4ZaIDY_=3!-u=OZAZ$fs~ltK}JT|s*8 zq)cKyT97;G?>5<4xtU@>p2;HPvt4{bUQSMCE;*T-SD4wh|EVL|OesvCa4zDLa*NvL z7v$w<78K5aLz$D$o>-8clj%$3fBOv6<~)R>dlrkOV(BK7zpRk`nZ)^YIS);#0`NWW zBEMr8hx6wA#RT0e01mjjGskdCOFq1B(iQGiB}UhYX^}Pm+wFON%bMH{b&%g5N!5^+ zF$@>e4q&Op#4mh~7eCF0!<=6pVr~9=6SH|@g>ZlVBzqih_}JfVxjP-I{3l=MRa0N_ zTXOn5blUU>6FXG;EpVgo#in6c+wo;=fBP0%LE*7e{I`7C?a!hWhw}fnb9{}o$A;`b z4~5qMX~e81Z<3khscg@TgSF+uu|uW*_94>eq0FW~_Yj(df@Mhm50jvY1~-(k{o;z= zaw+DYtjWP{e+{ui`5$+#FF%>5arsrGf9wA`XxegW+S{kpS#G={I}Vb*eBTddw;z4B zw@5b~OSY5mr^?v=k#uh%$E%z+2iZ=(cPQoz@bNje*Nm5CJK?)%f%}DVGm!1WVu)`k zKfyoJ0&Cjs z^@1NO*8lCYgN12n3%W4fL^+A>@Lbx$YvM@dtBQ**IdHHrWkE#R#ZO&It1sCSI&OQ> zS7{TTOPl{}+Lk@hX`yG+7DiphwU-w9ZtKKFq9cU$=l?a4k{FS;z|Ds?v3s1vwNacz z^@$Z5^r3eib3&CXZse5a_>?9d_e{~}#Yx4ur1}IAX`usKC$_EFfc>FVA<8AqYYvrO zN;#)Tt{l<)^&B~|;=bqTfAhmtDXpF(9^oQ=f|my_bZxcg@Dan(F8Ml~k+vYJX$#_M z^ZPWZ*h_m!7=j_k0x=kS3Gn$mJGV%Wg>pJAhI?b9fYni*-wc;_o#_A^b%&+0j*=UF|+ZuRY^bP}{#;Seaggj|vxHb24XNdlwJ_-Z9>?AuSpjeVMA zD<6k_wb`{t%eh1$T#^kV%pQKx^WedQ6&nNCWcejRtPut^L14@b7O|7x3C9G;bv*}Z9pj%_4Q6=`spt0f>`rn3A z^L*0(?e_occA;R{jNHQX>EXPI;jZDl35A)3;X&zH+2ix3hto3CGcpUpSyRH(3i5I% z{jW)tv3*1^d~#+u2WirQTw&gXylhUDj`qS+it_XG3JNnb%GmmF;K-o&}omrKq|Jc9b^}mgJe4^m)hhMI=RAUL5^rRcyL7Sxw(1Ma*t>#|6Kg2 zkB=$j0qrLKxyOgwGCoCmX8ME)nNzqAgfp`<`Gi=Q&#fH!5DHTnoO+l}r~Oy%M!4j| zg>*7;N>0|4DOq{BM|LRqo0pMgc#wVMc8tb{DDS}p+EdYfaeQWY0`0UW(HPmHfJW+Y zUT*e`mS&T~9F6uKxcNX^B~%LaC91>^B^>YT4GLCZdOiF4$T+yiweu*c)=x&kBA|I{+@ifHq6L> z=D`tcm{EuHpgbF9^dUVc&xRQq$1O2^`cZ6&;|Bdb`SNXv|IC9U*%A-wLAkcXLwZo2 zEiod0FkOsKFXUOeJAy6ud3)sl>A?|f0&ZN6vL2LY6L20xTMzz^CJ=5xH&qm79>!yc zm_v9lB_};Qn@%bg(!mRT(5aTc@4WQh?SEU2{YM`BebD;fQq^0><;&4K-=I>`C%X5} z%bl1tsi=UCp6K*Kcv@C=wmacT2X=?|oM*f(gJ}hH@D?sPcpE<>oF2|kpE6}yUcnJL z&6;36kd|2DMC&k3rU}z&E-94>Gw61r3DjC<=<@vUmJ^F>fi5d#(b>hqaC$~Y0i8|B z%AM3b+#<8>q_(a7ScQ|nnFX!Ge5X>F+Gz&ei8Sed>=zaONf~4m(P3>~IFI^8L3m1L zVIie^*p6_L@`q;f8RjE;D*fM9PfGrfg1iZt8Pw>hqH}VnWW(v%eD;D`;Dkat;rnkl zKMX4?9l`@Xn?je@>C#S7eqq-5>?7+$$H^8IJ_3A1J2m{CBTKb%{XGd`1V zH!HW0FuQ+dF71}HkJ?4p1a66PIv@L_@*Sc7f4kySy7IYn_^yJ|wESYBU_|be{LBei z6SHWhb~Idx2&fGF1x__vLcgMoB=LM}}Qjz&LV zcPgoNa9&X+T@TNojB;7{i(!9r`LKh^G#4+fknzg>T5 z>mxxMmGWT7(7uC*9EoK9jb`Ig9t`W*uiRZ|Qrr@KU&u+%&p&LF(0`Njl2uKMJ-~QS z?k+SV4`>X)J)4L9WNt}T%Uo(6^ef2BqyvDXG)tsk5G>g@PVxIjf5dX+@9367X<1SR zZcOSlzG&h^nk41qWt7ttrB%?2o>ZPxkXJ+}yJcV&_sZq_n36ft0KPwkW@+Tg;BuTU zwGR5x?c3R5H%~3^>5(hrHe3crFM0BzZSjcY$Habv28}4+{d4Mf9QXD(8pil%F3X} ztLU2F1b^J(9s$K=mm_kERIz{g>n>`Hhb-;0z{>RMCrHivg%b})>d zxy_yto|>0kNT-zDgI3vj{2G~Z3Kt*BgUMNwCYMwC^cy$9@XQ={zaHPK#1Ea8YXML` zFZr7XKu3;8ZA|tJJ~DcE-lR!16pW{v zB>5Byp8PAf?m!7eMe_?6il5HMp+k9k5F#IXXJ^r1*lrl##>kht{5^zne@1~T9DiYw z?p!N6{0}lmlMi&}gdb5qig#p$V(6X}c`pLyD}3K5-BF!4{v5iw^@vwXCYKgua`Llj?jQ}K)VWrd^wji%EO%3> zykNjJmcudCjV2!kJGC&hGpA2*XI$m#_>`=fNBE3uOo>9%QAv51n2!ahQ8+&mcH7vbB<8a9Okq|t`~gpcXYZF&eG;4sxolzGN7IT$EM{K(IrFQa>PNjClUS+ zzI&4oa(8F(?aNdLwAaX|S^fB|?5x5WJakPta(9#US3bCV%=zfbx`1Zk@2&d}GWd^8 zV`6&xH%CmQyECX?;4y>rlHqW)LI_T0|?Ij4#hr{N*%jychGluD*fLSR>%L_hr?6p|Hg-|(6u0 zb6DB#MKk=2o;;uDR{a~9|8-TIjMYbaEGo;3T6gq(@V?DTVZI zC^xO(-8juLA#{Vzgg_Zf@i#^Y$cMQ`UJB~eU!5{VbLx*rwNi5kLCi`DHPLATY z_Xf%S_xh4U?se79I`{xl{MJ{$+_LICT5cH?P16{lR@F}^ zUfsIZI$BPSQ(L#G)4O=}s@lA|Rh-v)x*^9Bchg6c*XaX3r7&<7QLohCZL83KkP@93 zSD)Ts$}ceuqEjk7!rwkpt=hA+92X?+rLQJzgiQJkV>+drE*r*lY0S%h!N$L*F4V{688#{Gt|wNhM`St*FE?bh&{a4AyPt*PK6VYep5M;gU* z!TMhliEZp6)%}_#Rmadm&ES|8alPZ?=^eGMR{PeSOw=v*iz}x6D{z?w(OgEg$TWrv zsJVAOE-jM%)ukU_tIAnq;PMtiu>r`r8yj-_Zx8mh`%BFthF~z(=<;lg%;Yxjqmm5~*w;?A- zIn`v}PTX>ZlPZQHDc^U}YW_w0s+{j{qJ!44QCt-jHikkOg!#qY_zbz_NcS1#KBL`d zjQfmrpKrv*xt64@21w2_y5?7dJ5dlQi~zC%#q zeIJQTnnsb`-YzO>TF0Qm2R;%Lw27?0>E~ljwAA2#B$X-w-Z)r#k3MCv5JM-ktsljb&>Wy(mZh&dDSgQAAqJ1d8v~9;Y7Hi^v-#w|bi)A3zwF0Mc z%h^cg*vME?uhDHsQuhfoPI|EubTTLEWP0didX`G2cd2ChA{p;cf2XlD)5iulk>!Y; z;~t6V9I;U@ zma-#Azm* zxfma5;v%s=($tyx2A15+tx51}n!8A%kF;A@)#zog~mzRfds1>ArJRw_)F zIPbqb6c@aI10&h38gEA=4_ibgK070{J0r9^BfL8<(UB>`)EH5@fs1yKTwnE zCh=Mkm2RT=88PDGXFwEd8=h}Vm#d@RM!ka&ZLl>QVSGY;z;MpXK~auAHoK)i88 zl}QvoN8(K*Qn<>eQ@mr}QwIde$3!HZP6FT6kMtRC6Om9v@dFZX7m=`p;sF(+E07Mp zfZ`CV|@YDlz4X^uSIeH^GZ8I$>}vp zm$+_)#($ZMpz&YsB7QfLJl{q9bSQa&6Y%?#WF>eVl@!^%Ef9T_Kl0x zLTq2c63S_9#J+dy>LT`|i`7HyXBVrF*e|Xo8X)#-!a!;!VZ?rS16T{h{&ca{h#hn} zY9FE@l=@%Dwa#un7U?H|sXi7Ra@+Osu~=`J=40{Ra)^&5dduNHmgFr*`B+77InKu_ zdCLqRtKuyu`&cz^neSsYyk((})$*3peXNeRob6-vyyc}n*1%iN^Rb5Ba*>ZU_LfV1 ztf{wL?qkhE@uyJ*(E% z^Rdo;ou9KOclGOP`gPrW=EFYL-Ou0~`Pc~|*X)}3*hzjq-^|BSy=4m@>*Zsue5{X; z@nIPG+0Vz?`dFHewfC`;ed-;3Y+xvnv)9?Rx>LQIXmvwEuK%Ic4GksUPljl9!@cC| z{@lXIP$k~szfCg&o(Zy@tBxLiGVqkdx1)eh59Lsq)2@$7IV+TM3jH>Y_a@uR21&hp z2FbbJ(TJqp6Q%^o`MxM3y7eC!q z>c)=t32%f_J|Y7z{Fd+8h`sG&D7JS($!r)GMDuYuPdc4wDyrh$(Cvg=RV3^V)$L4^ z-lW_hc@MImlv~sxNd6Gn_o;mpa+8$^D103Hixiw&5fPt<>P~~im!%}WDkbrCD3Q&m zQ*%x)r$y(~K}dcRN+skaqkah0eIAlOm6H4!l1Y4(IeC95kyz| z>|Ju(gJ?H?&^*`0L%w2o;_i-$4pPnVpu;n9XK+yv%>~3WaTg~+_MK zuhgyFj)G^`?i^1g-fg&#AaM*qx0dJHCA*PI+#Z6&nFQT#nZQmxgNWps1S* zg=+hKhL6;ZFQlUz9Ce23MeZK-YAMW&Eh3fs~)9VZoU(?xG{Y^iO!MfTxs*w*E59xb^1UFd#aE$ALI zok+Z!;&DktzeOJ1Ek+H3l$&Uo{(|7dT8x!tB1PY#e<#-?NZC%y^cMu_^%yJ59uy7L zfMv%Z<$JNbr50mlIfd37BbM<&N*!7z6a>3!F)p#}%rLoEL_D8UOQ%)7N_i;>9VnM7~E{K%c`yed0o=g*wEAhVs&= zk}?f~Mmj-BI5-YVgF_7{U;k~|vpDUDxQirlx2RH&3ymiKj1R6)aPq1AK_NY~=;sz<^L}WokVCu&7E4umi!Fq1fsFT;Ol@e2B6(1?y7bkNQ{| z5!&|86AX%b#?RE2-=i3}t_$fvOLQj@|IPEb#dViPBpf_gyJRi*fXejGTTMM=-)UT` z@09dh)(vwajYAFHVE__roE0<;@o--pr~+wJC_`qw$W;29OqJ3y%~LY=UNAJGdsYzI=ME5I6a>ffQbZpo8_9!A z^Rv&M67+e8C@tj%eZHin6JXSs(m9cS9}=O5EYMgNN(v&AyC)u>cwFLuD&6QuG9v(@ z1w@vz;4{h9)h4u$2!#ez3Gwu%VKAUdJCcZXTQp2Q-fPe%aRY*gnitYg6<>N4?KTsl zgAP?9YHaL9qh?~oAhBWG0Me>}G@_4d7|HP@+AfaVEcPn7NsyR$Qeq^XFRIvr{HaUn zrqGl5WLqiPeO0a!icC&S?C$+w2djX%+%l-@JSP9D(a~|DD}{#fyrOz%&SJxmFP0v) zBFOa`WD~7yYC5%OrC+OUs7Eco9r+#Gyw-p!l|q$cQ)|ww#iF&-LKlQCaz$0AUZ_Kr zp`lH^5=Yb+)i5+V`t(G$sOv505}_}qIG;XsOP77hNpUqbs1{;<-YOgoXIy2) zoEY`<;-uIYpIF0K-mWoaGLADz&QhUc!zRbN(rfBUq+tavaW-yNk~i|fX0187KvEIp zUqfmQbPlglsA+PQ#K;D98gQ~HRJkotV{}m2it3r_>zEo+>Mn`aQK6F(6R8>cdgfHy zzyeORb;W{KK$qE)oU85mi<@o-S0gbcn|CbLYN)YwLY)=2fX>L4)8t&8;Hs*ND;gxk zmEYB6P=If($GS!$jEuUuia=jNaUNHSkx_R@yPAa2@x@-CY{QA2ogXLYt*H;w_Q6SQ zgC4a`sFj<{ZNEoJgqtR<&Y+n=G#zzFh`KlS<=D`OsF!0`#YNFer<2&m2$Sa~^ zmsB3MBw|U`s4JotMJ2e%NQzgc*u3Z@iH}(lT2hI?;^>Nk7B9NU!Nt+nL?;)+>W*C# zeLoBrS6vdZ*u(pyABj#`Qh9M@A|>>Z=yz>0??(UfH^pC~?}{HRT6K+To!A#y)1TRtlf5H>@Bf&Tl&pkDu~;n5{l=^D+NXL;YAuF7e%d$qEcT+ zW+8XE{{5(HqV)!3!zeQDBCZj)tXI<`5syT_R%#0%x5Xh=)3L-a(Mw{q2$0+2(5sik z+-0|5BwN_kjLD3kv2?zuALn1m$U2nj?8LQWz?1WCw20trD9Ab1TqCM1$%;>-ks zD~AM>ETRNa@d8|r^*|Th74X;BwZy#E++zas9`g{Ax^Blv6f|-UoSCy2@d+S?riYeT zL&+(YDLmE0KV`7jaq0M*{ZYH)ar-NF#~1b=?T)qJF7QA6+z9?+IM%qHglvc9&VFS3I3th z&=2`FwIhOwBN#Mc#K#}H%ScBs6)@DjAH?m4P19vM9j@8Q&MBtT?fTL=z^QQF74JMX=YFSqf%8;%vU5}u z2)cm;oeF~1LfKDQ&MuO3>apbHDLHvcP9De^5s{O3OgVY~f5-t1o1L|@95#sX%T~>D zfLit=W-ZaZt1_8(C$44W>jMBe>|dEC7YVIxLd0eN(lnC*GwqkGGM)AT0M@#Y&*}Jp zS!dNM&aVNCvJbSSqYTsj?aBkd?~Hbtw(*JEQ3a5;!-fV*a_<7;+RuN57~fxsf|1dk z8M2K@1Wq{l6RBd_FVxkBv1t3cRkdzx13+tSs66MxJK>yrsLRCu|0WsTVXZT2yVG1u4REh@deF6OqX)y& zZ>H(T6Lk9dAKhy=wf5LZRz22qp`VS(w5NT)7 zsP>mM`r!Mqt-%P`>h~{&6>|}V0!?ip1HS4GMd(@ATv*E!m&bx9>n7%ca5x1j1pIB) zl_3NtoiJe6>CzZ0TzRTF03kP2DdPR@HP^16;>z* zY5*f>m%zHA`*AIJcx{CI&KvL>bDOJaUA@A3_!4dr8jYl@Ba3H172fhW3koaBi@oz| zy~Tw!g{s31?s5FauTc*5214*|DfC7=S}P;}fc9wGY(bYBZ|*b0&f4W!&$u)N-p)h@Y!K??*0B$0+ib6ty)LQO{yy z;l&E=7*G~N9p05(L8ioaXCdUFux z9%}M7G%r=jx5FPa=JC?sbI3H7`k@)nNH6K4nq;;8+;DbN(FENQ>hh9fs=x|+AVwIK z#1KSHBdHFn(77YTJ*Ru22ER(%Lb(y0WRY4nE!jjlnk`tI=&Knv6hK1J)DC z%7%}cT~gl}Z0Lk{4OE;%6rlHm$?>IxY7|o8Us6G@O0jO3aj^ih2uhGfXVWKXd} zxea`S2N}TVi$Rb@X>_FVHJ92$FVADL40g6D6GE%ekUCkA+rjI^94t3W_^QfXwuM4m@KeR{UI2&P}q>_BJ+{T9)0F}O5Ej!IoR95Ko%VMD#p z8&Dyo8R()A_HapiBf2cQb_afv3%@O)J6@fLgneHNz!FJ32dSo+gV&3e02lT`=(V)M z$C|t%b%6#}4@6~wy_meq>y7By1>+IL6logH)1ZAgkr^}R%ugxGnVgc7lbM&9os!WO z>TLH-qgP?zm6(jSx<0H7Bn3{#^(FAN$*NSsR zTJ1hjKRz|>ploOan;~v! zMikJgT#exvPr=A6UfMqd`B7w@FJ4EP*gXII9O_RcKwQDSLr zsn1%{&~P+MPL3}AzmS!MrVak4+WsI+On=J+T3H;5ev{E3Y+jy4ZNd1^CbJv{Lyax| z+{|W!w1PbV@X;_j2*6~W@;@#%wfke(Wg5PDpzjop! z3S9!b9SkgZk8(fW%G39%;V~K7%=70NW>;^olBDPqf>2`6SGpa^< z#i0*Q&-Z{Gv_I*S@V9l?oJX}Rh47>8b|T~Hw2%Cp#Q5e2L`PYS?-zI;(avye=+pQY z`b10rF6I*zuutG)>#iP%{?L!_Ojqa{W(oj_auzFC`6C?nz(3?)JW`!llcM(5!w&fm zDKgBz8))KZQ~!&ec!1z%{sMGV*gDkCcoFuVXjNIoYdt5l=b}koY48Q$QCIdyUBdUd@WHVOJUwVhF8bR13-mYN6@wpw% z|LAe>w=Fo18=8M2^doL4--^$%;3MFi=D+bc_zR5V-<>dPY5rjdz=k{c19%u$PuF8E zHjEStKE=Y1F0SP)VjTG~=V*M1g&&^<)c88av0bSa{8EdY6bt^eMGnS+mh;MSAJ~{Zj61kK{U=k+ ze}MVb_!W_p#{4?BY5v8`uSZ6Wzr;AkAZ{9m7b|Gfb1sd)#dwN39p>-idea1clh8jJ z&eNY{nNN@Sh+|H;5)WXbC(mJzB(`xB{KHL+zY$!ak!SfZELlZ5brLh692QMTjs?eh z+gZ^S&P zDsEUDSwCg{HzNp}#$V+r$#n`2<9D+|-7fGgtmmHu{wlZk0fDEp{!a<~cDBR20)Lb7 z&jhah?>_=h;i;K=A2iagVobWQ^+!j5O}96W+lAw}75^Q}$riYJeG{H(61a!ySpxsB z5^J0-@XHx*5%>&l?=pc;VS9E9{Ax}BcrApRZdWVQ*9-gy?yuVgo~eo(dj)<0>+_hv zPhh+KUEsH~-PC)X5q)54M%y=nzmom!7m2e!4`qFIyI$k?N*DM~EGI+Y16a>f1m4B% zEfM&?IWFKuY}$0ascZ*$;kpn05VyBQ;L$9nQ{c~YySfGbCimk8fsf;Qw+j4OZr4tM zU(5CG68JW*SG`&k>6h6Y7mo@4w^TEYLjvE#xX$<5J{y_uQ^CK5`|B%#S26yb!0+XL z9L4R{^2c+#(ghyP_RkdfdiJM0f#1aXPZ4+r+p|F6cXR$M75H|RUm@^VR2a5-0?%ao z*9yFV>(%oa-QK^kUDP5Rk^Z`y^T7E+&R>{c&vUe#Q<(37;J=;uKM;5nxA$v-+qqsn z5z+ETaeF;nPUC}^U(a(izKZ?)B*AZEe7eA|WBb$!+^_8;@V6Nc2>h_>7Gt%*7qS0b zBJe-4A6_l+T=v^r1fIch^k;#4S-m7dwVqE2yqE2(=N;Of z+8;g?{MsMB5qKs0p<0wI5_jKo{Kj*CYWex>Zzl-+Ii|-8JcaxB6oJoU|CuT9_gKIA z0>6{>X%YB8IbPNad=BfmMd1HseRc@^YNp`{&OUziHT&Ux0)Lb7Cj|Z=`}vCke}wga zN8mqko+6kE1^xirxlG_Cte=h# zt&g4Strz?U7~df99Palk1pXD*`v-wv#(w)Jfmd@}JSgzTSdPxSTF+0Je-O*lxYj34 z;98#?fopwc3A};xOs&BG%64lK_z$dSNZ@LD5%?~Kz;(O!2przmq3scYKd)fpIf46F zpMMIxmFdF*XVUml;JDBnZZX`?+MfFU$S{E)Wc!a7`0b3JDsZ)im0`>l_%7DtOo6}5 za+V7GJZ|sr1bznh*G7SlVS8RH@R7`ahrnOs_{+`o7q31YpznI_94s@CR7`sRF-&^{)_ki22VJ_)4ai3Va;%cM1F;$LD&1KgacM z5%?u+haCbR#QwHR;9kzR_X+$Rw!;$wSJpC&7X_ZeU(EDjfnUUW{vdE%+Y+}Z z)(cPL;U8F^WP!iPc&fnD*=~A%t>vs`K6<$b#R4D3dTtfCo8{ji@KZS+?-IDa4u3%4YB3VSI3VzKT<^;QzmW65djem{{6_?S zEw}e4f!DD9{kWgCo%5LpiSc^`{v`MNet}=X^m78gf!q6*z@1EgDsb(u z-wS*W+u6l?-H+!mU$Vg8VS1FnzvO;_w;O5Ga<;MkPZM|t+o3|>w{X2@2wdN9)qbJn zFK4?g6Z{{sebxzl5%X^m_y*R0r@*%|eV4#@GyR~zr*XYc3p|NwJsxWPyIG%q3I1;w z*Y~9~e<6>LHuh7Ef6I9_LEux_&Lafg%z0pfz(;YtQv_bj_9+uMuEdJl0)gMn^)?E8 zBz@2{7d%FCj>r~@s|W1!}NOsKUtZ< z_)_4Pv)$_Pk8`l;{(6r4t6ktt%zuHvpI|%uUf_B>-6n87p57vGJr4dw;CdW9EOK%HY$K&S$*YWs+z}Is>y7>=O+CC?+pAQtc9+%Suem470uE6!UULf!+_Rl#2 z|C04NOW?YH&lR}7f7d1OS*p8@iv)fm%ik*S(ais-z~5*5d4a1H9AWtifoF64I@JR! zVSm&2<&p%h@5`kMJcHvUOW>PX{xpGaXTPlwcrx?X3j9UR^FD#=`-(wl?-2OOEaxGCXLG#=1-^jgzb5e29M}I6xV|3zLEta4AG-L4gZBRd z&XY+3zl`fm75F%olO=FHK1~z&BOEWY1%80>MFKBpyEO~^Y}O|v@VD8X7YbbKf4#u} z!0o+5;Lo%C`viVH=ffujUc>(OvcS_=pZ5iRBe&~ofv@4X`$ga***-D&kO6GEzx4A2 zLj?W>`@_is&*gEVNZ=o^9Znbc5A26tfzN08+XX(E<=-yw4Qz*f0{@KbeL~=8sAd{3 z348(beT5QQW^R0w2ryWP`we;kei;a6jj( zHwAtp%fFi+4b*!6%=-UL;D2NLJR|Tzw)2Mq*Vp@B3;Z?KM?cV@>z#!Munpk{9W<`> z86$A5PoBWfVn44GxR!Hd!e&z&A z{6p}!a=o8e_`~Zp0e=)M*@Bz4kN!Jx662_kes%!)as>ZHZ0Bhf{t?K?TdCmxko{_j zz@ypD=UL=nsO;u^wL$RbFn+lOM^o~!Zd@gBc5~wnA^#A|d0OBD*biS7c#8Hub(Hl z?Kt>jjB7hz#q}N#_!W%*UEumTkCz30EAziC@I8#bC-A>8{t@FyqW=2%pLl+57jf*` zTKI=sngvJx{rCf0#&Pg6#&vt2;(99t{v_k`1+LaDH;hFBSBsk)M!mrGb1F>&S1Xnq z#!|+0d%t1*uMoJwoxI&52mN+Dwbn51vf${qKeGP!2>ic{KOk@q*ZYvb)ndPJ-%sHB zxtV7KKA7da!nkhNVAlVi7Jk%!66=4&f}{TWIh$_;KAz?OB=7>p_40YTe@|n)A185* zS1=wY@L7xxVqEKg3G1I?;Ya-oS^sPcj{199PM*MZJWdmMfcXmr-of}Rfv;kGj=(Qq zyqa;XzkZHnrNHaCT^Ah(zd^|FW%)M?{4&Px68KGw-y?864}U=5yP5wXf#1dWKWb1E%3`orU_ z|LFpMnDKK2{s!aq0@u&YHd}Dib1Bcm1B`3?96}{w>k<4~&#e|5_0-QxZ4>y{tk2B? z*MDccP2f)Ut3O$A)HB3#?qgi*xfssjc1ZB+dF-1O9Q7Ql^NGOq^WUEfTtBDurNAe$ zobN0+>bZ~SS9&t1^+X+U8^n`5?I-7E zr2>DCuOs}&!9xOH!}HrNf#+aDU~6N)#vQ9jxPBfDaqIZ|R||hCajKs~798d1`PC5% zZsk|2gR3RCct^caErnLbJ2moc3w@Ozlf75JM>>+u%5 z8|&52F;5ZvC!)fzou+owF67tub;|_a$^5zsc~_;rle3H)Zp zn*^?(GhQn2XPLi4;Cf!yDe%vj|9pY#dDLowySbCs2|S+h9)aT*uyDIn?ZS2*#rWj{ zpTIc2?}i(y#}Lse^s6u{o+t3dOzVC`Ui_!W3vGXm>+w$8P2>7{O?~(%Qm?)aH8O)M z+Fi=Uuv|+fuF_>>();9@^}(Q#3CjY(@-N7qlL@c2S;>4Lv5dmP=Nf_` zeWuY@-?;?dac}a2OggTsug9g2R6zy7l0Eo>8Pv5A$4s26&&cl@E9@Kt8Mo$2Q#fa?QZJg}8Oh!1Iey z=rK0rwQduDK$*uYuj5RYNzn(q&w)M4)Ok;z*7f5%S=LS0ulEIT@>u29u>S8@3&XK) zzb;?HwP<%&~4E?xIe(N)iJ1ZX zHH^Ot$B?|6fJb6jZtybvg)iCt59K!i&$09`c$|tG>Pu>{4nB}^@K5Uh2f&V1|C$x5 ze74$!e)0Q`UbsU{WTQ+|Bh@AYs`gFIFQlWdDiF>-n=5A$cE$ z6UWm3R@T3k0|WI(AC>yQ02s=Yb~>3~EL+9bH^4%hu3zu}0qC*H|9PY0pDY4Xm(%;h z$0`3QIu>kto~5XWt#tYC;DkQbJh&gM2X-q6$CB3t0LHaYGh9AJl-KstFs_-8GG+OE zu3!7F=9lG%K;dKcpYfC`v6%~E|KY6{U0%oUvFgSm9XwIh?`dKh8F-BHcrQhSC?JOM z@fO;J<4;jX+YkTTd93oAS^soY(6R}V-gfulfDZJdtNPEP#nMayTXS*au&`n?)k2; z`+2jlhBPnkc@$KyE!(&*4Lzr#ry?!AVpC&U!G@Z&!4*C46>r>?h9kpl z&TxloEbMu5{r*)ep;OX8J2z@qflOuH`=hZD3GsWtzMx;_`u(Apik>H+E>g9)=M!wX zs=QCbl*nchQy9Oez}fvy{8VRXdHmiN3-_TXOUiW38_tE%~28l&OX|W{pv)gqCg9>w9J%_sA88p=v%AJ}Rzdi^nBoxN)#gbt~ zfUYncRP>WpI3ID4o2b((%e0RKlz+!OG)r9aJb^Md~%1dhAV7pLVx6c(o?_X%tCbtsnJ*WqVv zDn8jL-f%(MLDDQca+L5!r@QIlY zqjJ;C=%&J+nKtZ+L4_M;M#b+16%cYGlnaon@RYaXH!X&?ATkkcmePG72G8`oRy^*T zs|)}0iz)y`v%5cb0YTvb682#LQH5pLr{xW=R6v4)zDvPY4Gsqw#1_bNGAEoV?)d_= zrnoCNU#jT&fljU8AAe~UILgMYlq3{W;X@b+`uzHM^eb*RcuGOp`tL#zq{;#tFQJ0i zzv-%UU&CSr|3Zsl$-q-Q$7~O`>6l1ex z0GH zfxfQ0Y{O5f`uLxCY66}TKASzGCom*ISq&L@-&ASNIsoN_3B{dO!1E;cp46cWN_ zpg!3RKU6k;WU-;f!-dd@!UZrOssSZ_@7rY;e*PMHISwJ4TGR4rKq-ShNT}EpN}Fm4 zsC8)vi1Wacu!Bz6RGe1&Bu$Mlh*H=tkc0EFg~dHTRQ7z*^LF#%yP}Q4eK@7LuK2~!dfuXu=qU{X{UYnu%oj-xo`Ko_I+;P zd+{AJs=WJgTg9oZY3|P3%XBCPBACF7k0LLdg zoUnKvmpEzQ)bQyEL;eWIiEc~jjA2FL(>sUa=}GSKcy*@gdpwm&Sk!U+eGrzQKcFQz zW8BH$SrkLggxLGx1QP=WJPXHesEit<*j*D7iZr|HW4;5R%J#L zF;Ed{$z@Q@U}HkMyQtsyaEKQshPz~VP58Lf9l>YDqIHa@*O3V%^h3*xRrRB@ewMbMHe7P5?WJSTZdQqo=ih9vZK%)%K#3l^q zmc3`93RAGI6AI!6;+y|a0&Z6g=|`&oC}Nl=15T*Oi8~>B1`sM(;zq{J1VTN)5tmAA zDXPq9VoO)Vi9}>8Vr&d9=#_88VxW?jpmd6kcjh;6>O@qY$HNzd?9;8{gv(wF`U6p>9ri=haV&2^)&G_Ruah-zeN64h~&m#dj< zuLaf-`3K{s{KcguPqkJv&05WLYc+*^swwJIO$k?{RG3LJw)zxRPC~XZHJi$HJ*Li8 zQn`IhRZ+QHpH}Biryk$MR5hL3&D8uvRDT~+3zD%v_cOJSsE?VdB`V>d5sP+3a~g4r z><__#%G92b!|^-wWL8gV%sqIZh*9K!D&cxYlQJ%KCXI+FMVv@%%Aq`C=uEUaGnR-L zMWhppY>W{{rJkr|10#3Hq zK^bbOXGZ*8a7=#r5FC(4KIb$>blvYM#W&EEYGq_vO(dc#bF0gSBf)JecViK5!L0sc zf!I6}RxC6;v*RZKu}wKS;^gEbjaV#6wHJj!t%Fjq!j5{4c+KXiei2xdKJ%^m%(v<@ zU+JT2U66o9)X?BrNXjLcOqBb+NNS&j5BZlXUn2({@W~R=hb{w)9QSSqa|FrJ7 z0zedIpzkA~Qu-`=6+r5^eRu-QbWQ~;87U*}hUZ7qjjs$-n(~-753S>XH~bB|&2zy3 z$Q1|i_POD-Q6u-AY@^%hMwHES37p-p>3%lKjCd1^_+xC*e*<08HQnD9eI4*;YdXmm zb0^ZUt`hMNu#tyk!}9<{53+gik?nj<53%*12y`<%KSbw;*^>SW<>#Km^a-}aXMsLL z(<5yO{{Z@|#e6>1)*m0WKc|uD(Y7RfrQlpmpJ+?KKJNm0YRQ~(!!zC%cNheGIf7T4 z&9Ft|x1qn+be1i89NriOBa%Wn%0O;SPqanjWGz9{la+x6YkG>JhX4&9HHGbv>FG+C z0e>q{PW3x$_^h;s&uSvr<1ZwF0B~X9}+cysgH;n$kZo94Pxpu zq6RZ{n5ZF49f@8GZ5hhcS9ESTQ{NDE0#n}-HG--CP*03x>c7#O!A(+_`jIjid^QfF z&GR!+6PWsyT9j>4<}*!l&wNGM)ts+DQBIS}6)P%Aah55npW>XWs2Igrqo_E=S*xgc z#kp8f{S{}UqTq|Q7+uYZ8lX5k6g5b3h7>hKads(cnBrWcs1p?DIz^3CoEsFCsyH_* zYP90qs;Cnc=Qc%+HTzuwy6iNKSe(s9;pmQGNMlZv8Y(doHH|Zq<(jq|CvE^sZv@&! zKR7rUu?`r2Vv&h1&TiJ+>1tN&NjGskN&!w|w{yIi@GrP2qeiDplM)3}*@|+gbGb@w zx1#dYIW>sMFLGevG0D6lc1kW+>_;MHMS* zf}%i16ijhqZ&WPt+O2{(hP23S(ii%a|&NtPN$cp^VOoX~gk(5bj8S9K3IVzG%HY_I1c^IBbHLXVk{4>JH& z$wo5~38pULM!@6|Kvi^;>49U?7CtnPfRhJRbW0y?FIU#EsdEri7Q0=cD7M>`ic$~W zdahEGi)-6vmh0MLuQd~J>Y^lGuREz7N677}6xX(+PrvL`6!*&wij(d32SxQ$GH+Cr z`W&w3CPne}x94VauhztMuQ~kUuqJ!^w0obTSd;sd3~uTDigI$>9xw|*2wC#ur_2O9 zOzV^$2NfSv&nU`Fz%Leho;9P9k>x*UMkfKqAq`jFqk-C{q#RPzK1IQGIF@@&p^UWch1FuIK> zytv1CED0@O1H55Q2Z#(1{kA#$X&@5X4bMB={Dk(-9K-W2H(%BEo=MXm8%ucK#P=3S zEt~CQb9fvGLlK|$5%F0c5ucke$OdTwTY3T_n3E`Itsu!n;1x1||0HDIEc2;0b(#&#pVBnG)0%AoCVND56?S6cC<_Dq!Jh_5{X z-kAd%z|Wa9f+a9@Hc_?&bjq8in?L5gZ3$}w0`&Fb86MV+gt z6BN~|s1b^4Q`AUSx7~g|s5`-Cp5T}b`ksa@G)H>SdUUJROrc}rglKcXE?^+zn1d2A zA*lA4gH^K2Ci&(NqRfek8mg!~MGaG&aPbJ6Ih?4d%yA&rt<*6`*-;Dp(+)f$?!@kU z4QNYuAw*9x8pXDs`<|j6r!fhSl#nG#$Y7ADgy5yKI#r`i;aBM`^6|X#7*b-ojqVFn zBNK{NRTtPA5s1iYvc(~BzLuqGn6GNsp;6_NL{c+ivCJ3{Y82Y;4>RGB5qm9U5L)CW z=Co+M6IlTN40F0kHo;@u{y>HV{JLx5PE0C7qj1WlH$rmB8VnIqh}?Q3WUdt%g{KPD zSZK~IT)woyFlQ)I7b2D-h1q*=T{RX@z%+$S#7u;h@MzH&OSY;+vP#V)j3j&S?o~w3 zGzVb#*n4;B6)6nk26zGY2Y9FnzLaiT2ER47ApD+gTMfVSZ0-WntToF_bCGQ&`V((h zC<~hVTZN$H(S^?8LKi^MJ~f$n$i7U}vFU$ghvup6q0z99#cY>_z|-Ul>XfmR@;>(H zRiHS=nDKOqwM!M0Z8e~Rrde&Tpb9ILj*lzRn1`2zxgt0==%z4^HtEwKAq5lt>qm3<>%D39R$KJ1zYZlEV36lT zs=%th3K#l~29E=$iR6IeHW-yS=V;b`z3ZeBc@ZU=A!YR(tB}?_qL+$&97tm^5-9=9 z-k{wB_U{9Vn|U2dr8u5F@~8wbJL{-8o;}qX?W*8xD?|xk_Tr-po=bmrQq>xe0A}|| z0JHlffXbs2K;_X1U{0UVrg)o^WNjrS0QF}SuGbh;-(k${6YiK2PVa+|qlkz_%fTII@Ck4NwcMXKSadcYwebU?iN2!!lzF!5>!%>QH z76s#Y#J^Wz;-Cz4RN5FBW{e2Hv~h~U9lc+I?BgenV7NHOGu7kh1$7`e`=$6=*C$5_-Ia7(BN!98SAKYU_jg`OGGL~QMq9}Txmp#Pk`* zEfFyg1@sJbVsirYiP~=By?NakFfdRoPDni}h$rM&W0Ash!gMR73eL7dMI|s9%kSCLk(+&FW7URunK1>pBG|3OI7& zlje$2M;Pkr=p8&m%tMj+FPeGi0icLw=zU@A9l(s(zX3fS8H-Dh#;sKC;9?h)DmQF{ zSAmAMIK%Fl5_4+IDKX_q`S9?7odKr?U@>nC2mB$-InV;Y1m0t1vpmU!19zFZ z(p*i4{Zipn76^q8cz}#li(2#HAtWH_9PjW=Ra-6$chFBABIhih`4l4?hec&jRL zF-xI%0zhuiKdf&5JgUl6EwT;TI|IBnG0ljHIW5Kx_a%~?$2bokzRO8s-T_Ib-4heT z+G2|amKoS0cn=z6yQie~l|4AqoIWJny0Jsblco;AQbSZlHc%uLOanLMSuhzJIfjvW zz!<}=?SiQX+{KQeK1uC|d?SE%T4-1cA zAAN$ElQhrVHzQ{LpxRV(QOv>^tTN4Eo|YOFljnvxm|)=f1LK5>AF4aCDS)@G-= z#tu=f9^1DDW&U)~<|w<3Lz^4MNy=ToTPKqrO&FvbPl{xaEl35TGvj~@{WMG0kG+xV zM78>xiBScE&nN3=_pK6I3X85|FX_$$BvwNr<*doZ9%BLw;=obCm4I3tNrrybfmHhaGE!2JFz&l?Hm0>>K(? zAL$<#0Y-yjNBaL)Lr((_LVG|u7JnphOH!ZWTavC!62-4f+NmKfuA65#c1CS;L+@>K ze;5PZ^I^;vF{<~zh}q}~_Yl}@XVktZv&OM6>ai&E9LHl(pTwAFJ3av>OIPiTdOZrR z?Ou-}^mOjPol&1fne!Z<0c!8Pcr{h6s;z};5nQK0J8QED;D_Dd3HxGSkA*g(?Nq5Z z-MgZp!*)gA7afJylT(K8aC{N-QY>IE0dEvuRRR_+0D7;DxgiGhz9Ht;7)LK$F*>%z zJsk(Y({azmDd5$(i#%oxm2q6t@4jeg-t$~B*6MiJ{aFlPWRP=|BI4K48@)Rk{Os%K zi(+7R5!7Waa@-cPD+YV$VC;{vfc_ZU69*2w+R+oYEe_~ypa5_^ANOh;a*-B`!kUwx z{@Z=9pIPfT2uj(ZxuC*ju|Qu2+L5Ha&=gO|P?v!rW7KHJ4r*62Hq@nHI>a-ofmfqt z&vNvD1kcP^?;(o&oO?t!Ki`~;3ts4GC%t7p#gRvK3w1sWv zc&6W{{eb(^eqZ%-1g_g=cWjQ`g%;Ws`{!5!Kvg?D97Pj1)dNsX&JM@F-PiQP>adrP zUdtB#B8L3L@e`OBD%=JgiWQ;_$&O$3`?eqW<+uI5>qmC`bL`$&p!de^i#4kS+k9pN za}(QqV#8vQ2JANfXoHyfqwNC+#LNed!wwZQhaHzkg=5A+Ts6S;sRP{SQ#cn16>zKF z<~MlF@{Rebos@djP7FF~cAM*M5H;&{{0$DEs{?2bG=2Wy6Ma ztaU*ZbJf|oaQ0%5Y*kN-Hyn4{@3RBvefA3-(7zWtwm5*=;+w4(z={GuhhvOEojHipkt&7491RoPts)K+VZM$u#$8OsLHqhOz0 zINwpW&4fVMX6`UmAnY)20)Tjsp~g{m6Q0pSIzXp0q}?5$c?`|lfmY3$Zg;2i5gLkm zFKdzmwAS3*$k8S2i zTRh#ZAqSx!Y6AV(kqf$-n-w_`If6?IxIV)lc(jC%h%}t0(U15Lw@)zPF(#Wc+U{v` zm`>9knB|`3bhy4r2GF$kGoAKiXSB-&>s7;TBpP7^wc{CEpmD~ zfT7nJ=R(FMdY_cUN8Qf;bY_v$bS-k%hFta}cOoEA^Dbw9d(14TZpkbpGm)H1N39jUQaApe}m;kt@A%m;JnFN)@fvyB6x$F}{uFcNmb8%-U*I4)}D|FM;)R2!`?}L z8u88@=pwkGQw~7yh`1*j8mhMUs|7*Jp*!vU?#H8AXC`{x>?CIl^yV^XBka1J38#V| zOg&u#dAxXF9X&%ceS0P}mvNvr(=G$u*-~?j+|p6n7>X)PCctsbIBn_BYG}z`Mkm z4W}}v!r?M9?#@+gTlzG)5Cl;WgqYg2LC6}npm#wV zcR9!9!a|(p! zK-cBaaVJ4&4m^!o_|WQF-NK>vR(GN^ZK`u~DM=|RNo0Z`ajfX+LLSHrAN~&DXQ^S&Zw>N7FLxQK3|77 z=nELZ*1BarT$szK4%G!hg^i7XwVdzsh75QUBh+0*2LHYv-fG=+`^*SB{kfD(P%D zu=qqO?)9Q&^RctQTiWW{4Igo~v>UB;!4N+77w$zYg;Y~pTc=S%+EUdG{!aL_eo0kH zGx&fJ^uyY1C=`0T)!)7(1rE_oya7Ko7My2!patwdx0&40D6IDfLPcmXlBtd?UIBu= z<#QGkR+JZe=hb?PVW}6@`v!NvGF&q_l~E486A0C}V28A$wSop1k@OK#N<(v9fV3u4wEA#Gx~A5;B|#(P4}p_FxES?*Kg2^j*pi%5sXNcN#Ns(^o#3us ziXHNk8l$bQi+YS5rZdP&ceYz=_IiWhr4UYa?VV5?`X^}Az65Ln5nT-ym{(F*Jg?+* zL03cn2AiROF<79AfUm*V0=-@o0JZ#`p^i>iu@8i=2sYHUwt}ZXhd^J?ZC(*n#&7j6 zA(Pc%QmFDbH3fZ;Cj2FBbseD45`*l5u7R-xdwAd#-Jq@^gw-ft(Oy;40&DW2B2ZrS ziSn+TNl@i7cB>}vM|2`k7JCeq^$Ry?PE}2LX?am$O?lNEP`bXer8U&j?rrn6HFT^n z3b9IzTdW3}W`Ga1Dc39VmlU-%7Lgl*T!{G43hL>gwbHq;0%B)_@-*P`E=Pa#`U2R@ zaFz(NqPk7M5wS+dC@u(G(zYj24tjUW#p9sZy(&$p~4 z*y3-WLuRzpgdWfcD>j0*R5%duk_L0b*;-`>aEcH(Ek=w=6ycD-j6tSrz^njW5J}zT z)Ih9)9ADWNdQm675ab_grmKM!DuXQ{(3AW&hmu}(r3!5b42)~+Qgor!kMxYCy zRKjX>u&V*Iq`ouAVX+Wvb4h#eu;#!z+da1VwhgOxxAv?pFK!dVdILlJERn~Wv zQDC9K4oIy=6-78EMN$JRG3u-yhUF$2jsDL1Rv5Nu@y!mh%<_(4U6aq>VfZ^JEvrBa z!YGHc0kRK{<>ke5AYX*zxTLaV-prCYMT;oyw;G+8iP1@0{ZKQ=(36L`#pMm505Whc z#EvhlKn20f*!Oy}pzXByTZOEwmhNWC;HcWd$ego604;E+z zFA8?Hss0EWl$)q)e9P*ZAeX|pU)@<>4ufqwhi(u3(_ntcsRl|^(0rfGq)6>5f42+DgnaRW1Bv|$d$ zC~0Yjq}9>t1ACT6e1)8xe)@PzaS=>t!3KfQ4Eimi-(m<4GO|jfI+)=SZ7!308=965hPv>5wQGiN>R?_l$@N*yv*#BjJ8l`yKmYOAIvRV8i1?|G&E1^ z%Ab@mDK8~sNlHdlZc0Ye-0~v$tqZPbZ)gtq+rbqwI@_1SY(4`P4{r4Z;Ux4%!_o|N zN*}r#EU^oWP@YdTz+|skpN3=!CmNb8G{it;nhqBj++saW8Ou8$NqD_=tvJ1IX~${D z!r4VMr)+7A1Q1N+aKc;;vrL}IvtcnyLeB-8TcAtI!Qe1&gXmP@4uau)$Y{qkfBiw2 zmvagxORHHMC141%Hkd&*8o_4Bdo=M3=n+QESTN{sm zHbSM55CRZXQL`@47g{hW5B#*ck_TousYYXJ8}R;82vyu-mj*YtUQKSykdQl&=O1 z7&*zoc=CnqUZI8~WpJ868(Gc%HeXgl+p>WAsrH5DHu|$5-&JP8+#?Grg6y8z@SBV{ ziEsQra^OTKt1}qLf{~%Mv(c9Y`OiBkF9Vhk@9fHe1;Vo^7Ko*_r9Nv(L&MQ5IXSxc z|3X$4T08igYGWSyO$Mzjj)1?(=npn8&!V;lS z4~7~WeNA)_fa@AchBy`@3*BPL2*O3J4+a5jOoWeO97vv9Mb0*YLuSCgcH$)p9S2El zE$l!oWQQY|Z#M>(DX6Wk1$kKKW@9Q{xq+n9U@7XNqA4lq^XuUbK_?Gt89ALq=B&!i z&CJfroH&k8;NOtwFH!JdA-#bFgCYK{Z9o2XivCOmdw30vHuL;>hS`;14vUR~S3wP< z08n@apEi2pv(%GtsjbvAc$2-vlX8ip*pq&dv)Gf}?JD&wwk?nLWEXnU3q2__JcDO= z5@vYZg)v42lwPbvs7Jp+#$~n=&)`e#vpgxA9Oa($OPqzC?2BB*p8W2pnV!w2eZ3j& z$p^V0ZH7e}eUcgF?PhuOE{Tv=hVsh#%1f{vQ(h515VaX)(Vy@5ZKH@hsC$!RMqmAA zc{bZ@Ue7k$T+dG1OxPC!5U|iQkf9msgEk5MOJi)6z*z+)OJPr(y%qpPam$XqLp2@7 zSHB+XEdG)ESesqFcXre%yZXE1G4Nym?bFWdYsmuk@E2A3o`EG;`T|VZ)jM^4&f)9c zi5C6I*8qd3`0#<)qqcF!!6zRFF9RIqYroO2Ak-X(zu`D|`*HB|kArVG4!-p`_|D_t zsQ0n#uorM_*G>*({chD`$Kn4w^C!@soFi}7=`@3PJ}q~ej-D3M^aO8?(LI8rE~Kq< z2Rz1^3EweQW2F6+blRMZUX zMKrVUJ@`SmRsTE-Zq(wI<>WF3ZhCH|- zjyw-z5W|MH!VPg2Ni^CDH;vaIE)s_TzkktJl!^ zb!IrH_1wb^Di!?uxIZcdu2xSmjClgjVP*9*t6KgIY=>6CAH@bcPvH1c4Q?ydF0AK` z=)ACPR=Y6%I2+?SfuF=f`|SeP-(LKaz;ELAJ|OUJ*8e$y>%8!mz)xrW-xav--@^j$ zKtsXygW84LmB52(6z5NE&w94oAc4QZa)t|hD%0Zx9>eq`fq%z(lneX@#`U+&wLZ(( z-|7W_HrvxL@b@_gE)cl>7I2Ti)xs!-aizd%6+O`DMuDrPM_~Rf@blSj`Wt*&f3@tX zVZ0>xf9CeSBk)Z2x6cK>5(j74^kZ>a{vnR9K^z|%|B~gXCEg6sm1=irBP3t+4^$7@kJTkzxT;OMOz1;#YW%)Y( zwEW4e|Fweu-)zr63Vaa9#h(TKx>Cn@NZ`1HFm4A0zJcl21%5sE*E<5A%D7r+E@FQ@ z4#jc1b-TRW-Xwu5ZQ%7TfnUk(#U%-G({e_09Gxieuh?$m1wNnSD@WkEe38IkV7gr3 z?=f8^@Nd`-^93Hq^`0s4dpRH034A5X2?~54+iiuw&tQLBE$}TIhwBA?k20gNQQ$AJ zKlcj!Jf8PlCGbaB{`CT1!f|w?z!z~p-X`$tx!yks`~dse9)WM>em^L1T-X!0*9G3j z^boeU_Mg8n|H%Sh$#jvxUt&AxaZJlu$?a831xNhzW9DxYa_(n;_?^JVGQL^hZtnNn z1g`VQ9)YWs91Y`9fq$yZV;mCrgX};55coFE!=DOVKVJPGfq%|%=iqj0`;@VqN`Y_S zeyJCD5&OSi;McI87YJPI*&}eR=T!pNdg^(;)-#d)`7eV14({Kl1g`abMc`V`_XV!? z{8Hdr&tC+t_0-oFQqQ3r2O7`k^63KC{xC`4pRzv`3w$;E;XHv?a-1#}xE@E_1^yuS z`vn4zwj3_mFy2c z2%K(6f=%^xneLad94E;vPvc`)|5SnB%Wk|~X)@PHzTR6{LEAU%bzuN?UG3&Wk;Ceo- zuiv%)x?TSj{BLr*oNOn}{|xtUe}T_oeMSlV4yLmOj`u8ZJ5Asn9CvdBK8*AKIRf9v zdbSIEG2^QQuD?Bfslb0>db_}{U^)6ehPJ~Q+}^(l{>9v1&j~!9+8%f1m4SW zt@YCNp2KnNK>#+5>%2Td;LF$_rVCupr>g}1JGQ@9;QQJBeu4j!+q+ue^H~4O1b!j& z?-ckxj%)ZDQlEB>Vg3gM{wVwF0fDdPdS4d!_nbf96Zomz-Xj9PjOU*}348(DUtjOy zX;g*98w0r=8h@Sj87=T@na&pYrz~iiz)xm7%o2DU>$6bcr!n3r@BwVM^924lw|AAm z@zXT8Z4~%Fxn0`?K9ucsv%vKqApa)t7g)|gf&aqs^_svlI8Htk_;BX`THqhE{=W(w zzgvS_EVoionlc8WuL`Bj>MNAFK(|2mgWP&l33KOxFth zYVNNlftN5H68Md5|Fr^tp6#<);EAlyc7Zo3wT)c@U(9y9U*MBj{sDo{VE$JGevs|+ zfxy)Qs)q5k!0Xr!`cKBXzx>=U9&U%mcQQR(;H|8WzCW$`-(@^s@K5IPSKmL={6jcS zYXpBUGQd_Z@OaJx9Rjy=y?TD9<@?z_mk9nRSAPhda4Rp9#Xd3yx@2GfrT+|T*{ z1%XdyJHI3FYncBFffuo!KM5RHz{f3``%||UU$@3hE#(`TpFGBVV+8*U#crrYbtC-$ zV7uKS_`l|SeviO!WB&aD&*64GCvZKUz9n!yo_;EDJq~^^a6JyXIR9xo=)9dQaGjS% z30%ixw!n2fo+j{z+5Qy*cd?(JA#gn|Hw(NUw`ZBa*Rws>349&L#TJ1VFuqgZx_|Ey z_%%GAd{E%}?^sU@d=|I&HGyZd|3u**iDA?Be}^boXH4KfvHzq9yg$cpxxn>(xrGAP z_vL&7|A751DDV|5|3ZQPz;@m&@Nc;vuM>C$$Kh=P*Y_3o3Vamz%i{tc&T;*sz?X8p z?+V<@aeYMK-*J2G{0CZX|9bAPc!5{2{NVz>g!|g^MwMh z=X!ktU%~bX3cQWu;zEH38NW>6D;U3C;FH*Hy98d%`s@?SYw93Phh{T68IOa|JefH z%>J`f;CddmLf}1Y{|y5FBkR9a;Ja9#8wCC}=cPXhJdyQ0DDYpn-q!`L+x3yaXR*J1 zEATVf|MT!+5!iIU*g4-72t14ZWRAdpWIfLk_=QTWajwA6W&R$47qI=W5co3os|N)h zzK#=k47c}HfxpCZeiisSo-dBz8${X;i+EmilEClb{CS?hFJXV4%nyiYIkjwuVu3GV zd-?@_I^*2}-_7>v6}ayA9Rk0F`R^3?udM$A0?+04J|*xkSf5t~uAfW$P~cy(J--n+ zeoYTI6Cb>UP1~o7^@$hw1G--XzK8XhF7Sig-Wq}1x!yW~*D~%G_+#upD+T^7`|U=7 z2UyRm1zyT=eW$=bV?FN^_;ilD*9CqL$LTKuU&sDD1Ro}|*z-BgGp7k$-{-0kc!er% z_ymsMPsHta0{<_!>t=y}#QN+P_ZZg7H-r97Rv#@vFyzqa6Jl)h2;|!E&}(aFp{okF(cXaFkQccDO;{^mT4H ze~Sf2Ii)OTkHAY<&nFny{)t}<#qCFdf6jO|KM1J#pJID@1%4jqflCFh*Co4wah)f3 zaQ?Yn@ayNZ9~S&NUJhDtw9gxCpXUYs0^=`Pa8zB#%i9(l<+Mo6S(?vreUO5 zaI9Cy#Yq+%>uqK^nF7~;&z)|;QO?QiCzS%%f6rKG!BPIjEdNY_uV#F)1xNYM@pyEu z1xGpeGk?3lcQYQe;3(&NECkz13yzTfd;Nt1S6}rsjEgKd%GtvGwb_EBoS(Sftpfj^ z@f!uMpRf9(z++hcy#m+IH{K`kbD951fj`CgL4iNc_#q39?RuK?_8S%)+w~Unzb)|B z7=Pb_qnsOgp!!PS0gj^|EjY>_#QEx1fyXfJ;Q>*{MJ3~j0xw~FkOfD5F5!7#ss+b- zS26#I0`FivlW|lVyYMTYZlx9+<^PTOX9-+CKU6F551Icgf$Qgl>KNDh z==%me0?+4uyhY&pd91$+d@k4fiNMcf+=TC`;HK++hH;O;^=Cs)5jZSYPMcTY^B7+) z@BrgC3;Z_5-w^ntjDIcg1h&Jk0-w$JNh6eAx?TFY8Lz;fXa3&{Jcje$9~jr;n!f*g zhv46>sxuxD{CZq_#)4y$Jlw8B0*B8O(Dt$gCqKuA!1hlIj;wji|GvO88UNUVqZ~c1 zeQm*!wVL_A6S#iP>_-cZa`g3rlOJf&@vHl{pTPC|A%g{eG1og>;N6U;T5zmak89&C zII>>P{8<9On(+b)j&k(4Hb>xkT&uONf z{uJZ)3;bcm4+#8w#-9=RzZw6#1;=*jaqUeDj_uNa@BXL2hjLthV8Kz29@oAWxE|Mj zvfwEH6qaM800Eo!{~X5SQwR>@`tKJ>0@u&U47T8?j~>@XS#Yd(3(FrP@HLEQF|PAo zI{Qz)g&$4Hw>ylP798dOgXPQ?_?wI`68J}qpDpkY8Lwwt>yyFytQWX`Zu?S;9Mm%v z{^53g3hl!7{Fde4An>mk|C7MoXjIt#oI<;>92evJ7}t7su%7=Ecseq`=4Qn-e>USO z0@v@wOc8i1^UoCcU5tkW{t@Gs2;AiP=N^IQGX9CcXEJV%Qtd#o2wlYZM1eod_?ZH~ zobA)XxE>$Z^8BGg@T=P3`atmO@v+x}qaP0Cez`*6iHu)k!4cAb-?_?&z-KYO--08g?+-t1!Li;t=6_D$XEXjU#5N;(K^~CFL-3j3mD6mF~$Z1k}cUITi#`` zSt1)*!usPy(P9h%yvPO=;lvJ*K;mFR2$v*ILPFRHh#iL{m+%iuLKfhfU_t@}0s#`@ z_kE|TW_nt3mYcWS_kOSRQ+L%@r%s(Zb*j3$r@M|LF?G<7x0362lcmi>==sQIfzBAG z&*VdVgH9h8z<29(RRA~Z+o=Kk=Q=$-fMeHM2`2~eF&aNDfH!J95x~1NzA%8B@A{Sn z@Lihz%m99!#?J}hH)?!M0ROti+XDD~8owZb|5W200sJM6_XO~_G=6aaH{Y>u4dB@! z1?&vqg&OY*;3GBunE-B{UtJx*r)c`?0{Bdg-=uMhtsz0;8Ykw**9LH7Ki?1F=6Rrb z@7VA$&(*D}u8THcn=zcbJJoreiFIOg!z%9IYgNe}s8#1-E4(T}*S4P5b3V49YTJZ7 ziLW{DJnk{7Q`nG>8@Q#Dufb-H=VM}(aeRZl|vIg$0~%YpKNjWp*O;{Le`N$VX>Izt4~`U9W0kmrB5 zJm;YxPx3eQ|7;9XsSu>CM%Ga6ANYhS6VVeI+t2H8u>G@v1$naM-_9S_$3+ZCj;EOT8}sZ=>r{vKqQxS`>> z=nQ`<6%W!LL>8AY0WqyIT|S;FDlwx!1HX(2c?QeR(B-+8mOli`{}P!)_20TLsZy(T zVfG(i5i#XW{|!|)i#(|p1g8H>fSG$1=9}`g>EPVM{26EQXjw1GF?9$u$KvZ>LzRD0 z%l}5IsKkQhWhb;vlj9~#F%g_qTpg!RnD`jZmn;e8?sXzZR@llZK0zf zB}9!xYp}mmiS=&YF&`!R{`Md_yiUBWttxL6M-=w>8C9e5dw)Nn`pDWuzdLpU;6z_t zWm)w=qVKndNd>qv(ce@VN%S{XmaR=p%oeI1FVXwxghzzL$5ZO-&rS5tjU*=KB__^` zB>FNFeX}E5yhm7HqW5(>;aNZY;wc(`#8)_Pl*qFp(f9H4zIPK4Yp_tSNsWCkB-#gh ze?5Fo;-SM+`yOr|sF|H-C7hzlMBfVQ*hktQIgAcVICYS^uRfAE^~k|5o^n}1qW57t zF-cXv0_{XjKiY~msh0O-uuW&5t6G&es?HL5mM8lD0%77u6DHB`Bxbsm-DC3h4dmT9 z(EGMceUWXEQq^#dr*VyC&M$_s7P*-!=CAHqrN% zlH%!y4v%6RW~3CF1BKR{eQxbJwddA;!t_>B9)JA}eSck{Y-vea{RtTO?)8;1Qwt<8 zQ6)j5?@c2TGWt{{)dRa;+!Al-pH-RI-Bg(^`g)?SGJ66vkuGc^UDze*!luR`^;g^U zAQ`McKfq{wqcBoEAY2>z9!=~T=pIXnT_dJcZW4ZJVbYP5NoPS-WsQ9=L0PH|LytG9 z2i(q8lSG<54%y%Q=mqXWA`sk?-)r=bHcYFqigUgW8Lr$zXDIrNkV-`~C<|MeVMF6e zPehRn^-1b6u7}Me)!Z98u7uagj0jg~8oF^9UujVVzdmv;;-eykpHD)MRWLilPO*1> zS~>@iyw4kRvm%F(kX$#GeHr39QC$3G#3NS0{WuA+>y<`$J0rwic?Rpk22IqAR3FYL z{xsqlszr)_#tE@HQzYZWF^sG78Pg>Zv7-DEQp#tX+>B`S*=P)(VVAUb?FRa5T9-vcO5 zY(__t(J@MjqOXI*!#bdtw2m%W={%1j1;pcktr^i787CtlOiqp@qo-uEKP+J~-!M5z zGdb;;5hP?wu?e6NsD(`xeXH_yUXJRPlKsuM45usf2H-TBvLp-oErPaBm zik=@uDSDxjN10y$k%Xu|+xwb8CNtI-j*$lG4E-b%zx6_=w!OlEBWnF%_;Z>hx#gR^ zk3V<{lh2J9lF5=mOs+CaN|KViZ->NlM7&wRKsB zpFyWkfo9)#Vfitpww2MsLVKHXnA)B%R?a9wvc6XEU4jy92sBLnU{cN6Pu8$YP78T**hY7i?`N*INze<_P#9it!TKV!rwLpRr$*( z==8x<3;fV9xN5TCV<#0vF>Qq$A#{-}$@5EJ2Z6Kqs;#1^b$J{RCM2UYdwxozJ0^*9 zlzcgfqs@^nD-y4a#K)!d!dFP1&@@IbaONAj6x^1DaLG$@`xqMhO_Ey*=hVQBAw=OQ zN@<7E?6Ne?UZh)L@1yyM&X%PO00m84Yg&u5f@b;EsGx)&63sUG%ZVyiBsD9lJmcu9 zd=yrqk7i%mfv1WKOZ?4B zP6(XHAcmR{#AXv|!!5<>JBW&c62opY_28W3#Q86sQ*Bxwjn#$hef&_a6c0{oD`hUz zxF~W?#II^oL?%)2wyTq7fe8#IWlzxq?WDA-HYBUM2~~;ECzZX6pJN;FDAxgE0izA)ejTy*al0g`dUU0~Y6VsMwIb3~YH4ajFJmQT zm?rf?X})ZvsR>9Uf+Uyc`k^D{>o-wg1X+z@x7#bm(P@G`a*CvyB4(%|Ir53mw%oSWzOH9LP~#>NEH zNEy%ac{6Q}DTnjDJp5rwB5O@VM#==FRInSYP}yZ5!3{?Po;^7wmXmvO)+t#tvzT-f z_NDgLLK{)fV@`#wvO=YI%`U}KFULnd1M1AEw6d~J%W`0ekt+N2a$)Ap%o<x%=!9dm&M|+Ff^s2Pyfo0H3fDd@D+?@AtsI@~9`tppd&U&oOi;|qB!zUK>b|kc zBJiV#G+tW@B^#e4*9_xX)1)USR9UB&&nn-TORKQn#mr!wd(#Hprz0;!;F9< zlUxkb9f&+h^vTFUA1d@WMh-CNK;*j-xHA84Zdotta8SvEp^Ngz^_BrlV zk=+rbc1J#klvE+i-uAv4M)9wPugV}%#@-C&7kzKW=K*Ls_igV#!tjm$AK`;xd%1fs z{CQGoAL*J62$q5XKJ3qRKcm`A&Wh5rfhLHD-%6%Wn)iuWrII{TG($WzTc3+ zODA++^U%<*dB5~f?=KZy(|%XzmIvsTg6?Gx?R{C%p}j)qZ!bvTF8*uYe|c!{ev`IJf#?hF&wVJ`ccXh3 z@w?pnTy)oc?qkVp_kQ;Q*Ipt`bZ>Kh=YZ^Y&X-g9N1W{@pG59J=rIbMfVJ$}FY73XU@s%gOMHv1XO=I%509U*kj9iclz zir1Z?`v4eTx7p9(8sa`@zi*?)_wD;g1uG0W7*dw|y#2gg-euidmCa|tw^j|m)yn2B z_`~*J9l1J!?Dy^CkCKh+49kqnJnS5kEF(2kp@tzj0w1D`5!K2@bFyQQHkPlt<#=SN zg$Sl8U+_O9^TF1KAm%i2B|zJ@|B{=-6K+rW^dFH?Sdt$-gzQnFJXt-k1dHg}sVTL# z7b}y4h!r@~yXSar?aGyi`0`7Tx6899;`DA& zFPuEXwe{X`Xl${RT3|;C;N?6#S^2;Yp9v?(_LDpSRa#+P36Nm zI$T(z&f3WQ`24DP7!?~zlI}>7f->_Il$g82Zz|l!bf-3 ztVnki7`}!^wPWt2i|%)|=XMqVVfd_r2R--O5VbI}CmxPe>t>;s;+}gd8f?R9wmWKY zW58>{5N%X2tlKzDgCE2m_&U{2n!Dg5a4kGEYnN-{z5~vf@!E&-@TMF-((PO4ri-k# z?QLzwmsOhYx0Itd`aFa)GtDFAW*X0&eZHK<(Pqk(ARkDlxtOHiFL=!A7VnKMaA}s7 z3;x=xv~j|yU57Tq!DS2bl>a+*w{GcZ zhvz8BDn7xJULMghw7^KGD@?i{)o%JyE~UG*!$(sfDTOFfc|4Yi+d5n67%hzsUX+Ei zc2T*cDM1&=%}bUwEQZs(Mf6=8bZeY+?IvD}4YyPywa07e8mTwUmJEHm>G{1hceSo> z^SNA2R!1{pSBqCSF@>}&C=~A+HDA=)om6ZK`;<;*sM?fyG>f6bbs#!f#86pu8r|A4 zl>a(uXwucSbdNHO^v3jT6dTgsetmtdIy41nQTztgj2>5wQ%z56n(iE7p!AlsLbgo$ z+4w~?B1wOkk(~x&;HAcn&kL?}m{>2y9b3}A7M!(TP2+i8sdVF-jhmFVd@`BFW4ys? z=sQnk!c}JXCU`P~Uu^1EK=euThGd3xkCBn$aoSS-^FP&7Cqz^N4svsGXUebd+7XGVV0J)Oof|m=|f5itN%Z$K3gL(XfoB8CsV)B#={`TPgkM91Y zjz*!jEb}CdkCRqwte@xl+}P!zy4*atTs1RD-7buzKS#m@-z~(z3{X* z<+4m6sYe~G|2Cn}?{uU7P_q(%TgUuJ=Z)n#ksCWFHzIN!(ztm<%(i_Hz-o}rmiqzx zsK7k<1N;pA6>6V{@adQIdDszJGNjK#@>rsI+%g0Y@tX+$fb^TV%?@2U6cg&~8qGs~ zL4q7t!Ea#MWq&?>SI9Dmasn2ZzEc*`s0^6&)@F10~m`96-f3!&*z{# z>K!ERP(BB#1HW%UW5aRa?Ehe>0zmq>&Jg=XyaxY6(Juu~`VO2KjLok`ocL`75yb9s z{v`g1*J%BVjRU(vuN5OAC3VxU7AD2f2=FJIwn|T!amo$BirZ@e)e7UfIZkQx?#|mr2~q$sLOi@ZH@As@K*-uO5@@K;Ht< zf0ruDB*omnWKOP}QnwyvEhR|WI@jdrh4Wx;T!0%QDIQ!qr>{$eLE2i}6c%u>NGTYM zj&kFhx<^!;gCeA(J`rT;ohCrDKt2+^7ELG8pc)6KsgpeG;tzqw$KM(sp6$c`w-2W+ z8v2u9zYH{MKmBSS?x(-Whx_UO;=?&d8~$c5-XMRoXAE&a{Y`q_AkOQe;qxsYUhcz< z&mYnc^Wm@h=>2k5>iHzdf3gqv^RMyYe*W`)IM-K3&I^1vf08t~*`tN^`sI1ZM_=fp zf6j;VGj7A@zkGPyhrj8=D|~n^D?|v^d#n#1gM7o^T$!2ex2qLCeR7Rv=v#d_*DeO1 z>f_J%-VJW{L}50sc?MtMqvvm(2IuvkfwE{4NgFwd^HK2&I6+O$2)@fN+Ax?V3 zlUeLj2AZ#KilmQ|qoNP%I8W6+ob{SL>t+P-3pBIS1Gur9xdHqs-G2)MxbfMxEP($} zkGItU-26DRK7h~A`nfoOzpVNAzujZI-q-S&b&t`r+3V(p0H0O5y$1rg@p<>%0Dits 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 literal 0 HcmV?d00001 diff --git a/protos/cmvr/config/biohead_config/biohead_config.proto b/protos/cmvr/config/biohead_config/biohead_config.proto new file mode 100644 index 00000000..80c7d052 --- /dev/null +++ b/protos/cmvr/config/biohead_config/biohead_config.proto @@ -0,0 +1,44 @@ +// biohead.proto +syntax = "proto3"; + +package cmvr.config; + +// 眉毛配置 +message EyeBrowConfig { + string serial = 1; // 如 "64:0~3" + string offest = 2; // 偏移量,如 "90 90 90 90" + string jLmtUp = 3; // 上限,如 "90 170 155 110" + string jLmtLow = 4; // 下限,如 "20 77 90 20" +} + +// 眼睛配置 +message EyeConfig { + string serial = 1; // 如 "64:4~9" + string offest = 2; // 偏移量,如 "90 90 90 90 90 90" + string jLmtUp = 3; // 上限,如 "90 150 165 90 120 115" + string jLmtLow = 4; // 下限,如 "20 90 90 25 70 75" +} + +// 嘴巴配置 +message MouthConfig { + string serial = 1; // 如 "65:0~9" + string offest = 2; // 偏移量,如 "90 90 90 90 90 90 90 90 90 90" + string jLmtUp = 3; // 上限,如 "150 110 130 140 100 105 110 125 90 95" + string jLmtLow = 4; // 下限,如 "70 30 80 80 65 55 45 80 85 90" +} + +// BioHead配置 +message BioHeadConfig { + string id = 1; // 设备ID,如 "bio_head" + string serial = 2; // 串口路径,如 "/dev/ttyUSB0" + int32 ctrlFreq = 3; // 控制频率,如 50 + + EyeBrowConfig eyebrow = 4; // 眉毛配置 + EyeConfig eye = 5; // 眼睛配置 + MouthConfig mouth = 6; // 嘴巴配置 + bool enable = 7; // 是否启用 +} + +message HeadConfig { + repeated BioHeadConfig bioheads = 3; +} \ No newline at end of file diff --git a/protos/cmvr/config/monitor_config/monitor_config.proto b/protos/cmvr/config/monitor_config/monitor_config.proto new file mode 100644 index 00000000..368c4660 --- /dev/null +++ b/protos/cmvr/config/monitor_config/monitor_config.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package cmvr.config; + +// 文件夹监控配置 +message FolderConfig { + string fileDir = 1; // 文件夹路径 + int32 maxVolume = 2; // 最大容量(MB) +} + +// 磁盘监控配置 +message DiskMonitorConfig { + string id = 1; // 监控器ID,如 "file_monitor" + int32 freq = 2; // 监控频率(Hz) + repeated FolderConfig folders = 3; // 要监控的文件夹列表 + bool enable = 4; // 是否启用 +} + +// 监控管理器配置 +message MonitorManagerConfig { + DiskMonitorConfig disk_monitor = 1; // 磁盘监控配置 +} \ No newline at end of file diff --git a/protos/cmvr/config/robot_config/robot_config.proto b/protos/cmvr/config/robot_config/robot_config.proto new file mode 100644 index 00000000..1838f1eb --- /dev/null +++ b/protos/cmvr/config/robot_config/robot_config.proto @@ -0,0 +1,88 @@ +syntax = "proto3"; + +package cmvr.config; + +// 电机配置 +message MotorConfig { + int32 id = 1; // 电机ID + string jointName = 2; // 关节名称 + float limitQLb = 3; // 位置下限 + float limitQUb = 4; // 位置上限 + float limitQd = 5; // 速度限制 +} + +// 通用的CAN设备配置(手臂、头部、腰部都可以复用) +message CanDeviceConfig { + string id = 1; // CAN ID + string devId = 2; // 设备ID + int32 channelId = 3; // 通道ID + bool enable = 4; // 是否启用 + string toolFrame = 5; // 工具坐标系(手臂需要,头/腰可选) + repeated MotorConfig motors = 6; // 电机列表 +} + +// CAN管理器配置 +message CanManagerConfig { + string id = 1; // CAN管理器ID + string devId = 2; // 设备ID + CanDeviceConfig left_arm = 3; // 左臂CAN + CanDeviceConfig right_arm = 4; // 右臂CAN + CanDeviceConfig head = 5; // 头部CAN + CanDeviceConfig waist = 6; // 腰部CAN +} + +// 人形机器人配置 +message HumanRobotConfig { + string id = 1; // 机器人ID,如 "hc01" + int32 dof = 2; // 自由度,如 14 + string urdf = 3; // URDF文件路径 + string baseLink = 4; // 基座link名称,如 "PELVIS_S" + string jointNames = 5; // 关节名称列表,逗号分隔 + string linkNames = 6; // link名称列表,逗号分隔 + int32 bufferSize = 7; // 缓冲区大小,如 50 + bool verbose = 8; // 是否详细输出 + int32 updFreq = 9; // 更新频率,如 500 + CanManagerConfig can_manager = 10; // CAN管理器配置 + bool enable = 11; // 是否启用 +} + +// EtherCAT主站配置 +message EthercatMasterConfig { + string interface = 1; // 网络接口,如 "eth0" + int32 cycle_time_us = 2; // 周期时间(微秒),默认1000 + bool enable = 3; // 是否启用 +} + +// EtherCAT电机驱动配置 +message EthercatMotorConfig { + int32 slave = 1; // 从站ID + string jointName = 2; // 关节名称 + float limitQLb = 3; // 位置下限 + float limitQUb = 4; // 位置上限 + float limitQd = 5; // 速度限制 +} + +// EtherCAT机器人配置 +message EthercatRobotConfig { + string id = 1; // 机器人ID + int32 dof = 2; // 自由度 + string urdf = 3; // URDF文件路径 + string baseLink = 4; // 基座link名称 + string jointNames = 5; // 关节名称列表,逗号分隔 + string linkNames = 6; // link名称列表,逗号分隔 + int32 bufferSize = 7; // 缓冲区大小 + bool verbose = 8; // 是否详细输出 + int32 updFreq = 9; // 更新频率(Hz) + bool enable = 10; // 是否启用 + + EthercatMasterConfig master = 11; // EtherCAT主站配置 + repeated EthercatMotorConfig motors = 12; // 电机配置列表 +} + + + +// 主配置 +message RobotConfig { + repeated HumanRobotConfig humanrobots = 1; + repeated EthercatRobotConfig ethercat_robots = 2; // 新增的EtherCAT机器人 +} \ No newline at end of file diff --git a/protos/cmvr/config/server_config/server_config.proto b/protos/cmvr/config/server_config/server_config.proto new file mode 100644 index 00000000..911b12ca --- /dev/null +++ b/protos/cmvr/config/server_config/server_config.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; +package cmvr.config; + +message GrpcServerConfig{ + string port = 1; +} + +message ServerConfig{ + GrpcServerConfig grpc_server = 1; +} \ No newline at end of file diff --git a/request.txt b/request.txt index 9a4d60e2..9e9809c7 100644 --- a/request.txt +++ b/request.txt @@ -33,5 +33,6 @@ third_party/modbus/3.1.11 third_party/visp/3.7.0 third_party/mainif/0.0.5 third_party/matplotplusplus/1.2.0 +third_party/soem From ae8961f52a744117c6a256a06be9f11bdd481511 Mon Sep 17 00:00:00 2001 From: linbo <1034003879@qq.com> Date: Wed, 17 Jun 2026 14:02:07 +0800 Subject: [PATCH 2/9] update --- cmvr-es/main.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmvr-es/main.cpp b/cmvr-es/main.cpp index 24f189a5..b659f177 100644 --- a/cmvr-es/main.cpp +++ b/cmvr-es/main.cpp @@ -29,8 +29,7 @@ int main(int argc, char* argv[]) { } ServerRunner runner; - const XmlNode config(config_path); - runner.start(config); + runner.start(); runner.join(); google::ShutdownGoogleLogging(); return 0; From 59944d6c0d26e19c37f7ecdf7d43d80efaabc976 Mon Sep 17 00:00:00 2001 From: linbo <1034003879@qq.com> Date: Wed, 17 Jun 2026 14:15:49 +0800 Subject: [PATCH 3/9] update --- cmvr-es/data_center/src/motors_info.cpp | 10 ++++++++-- .../robot/humanoid_robot/src/humanoid_robot.cpp | 2 +- cmvr-es/main.cpp | 3 ++- cmvr-es/service/grpc/include/server_runner.h | 7 ++++--- cmvr-es/service/grpc/src/server_runner.cpp | 16 ++++++++++------ 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/cmvr-es/data_center/src/motors_info.cpp b/cmvr-es/data_center/src/motors_info.cpp index 0a5af0b2..7cfa7855 100644 --- a/cmvr-es/data_center/src/motors_info.cpp +++ b/cmvr-es/data_center/src/motors_info.cpp @@ -96,7 +96,10 @@ void MotorsInfo::init(const XmlNode &cfg) { }; // 创建 MotorManager - motor_manager_ = std::make_shared(); + if (!motor_manager_) + { + motor_manager_ = std::make_shared(); + } std::vector > tasks; @@ -234,7 +237,10 @@ void MotorsInfo::init(const cmvr::config::HumanRobotConfig& config) { {"HEAD", head_enabled_, head_can_client_, head_can_sender_, head_can_receiver_, head_message_manager_, head_motors_cfg} }; - motor_manager_ = std::make_shared(); + if (!motor_manager_) + { + motor_manager_ = std::make_shared(); + } std::vector> tasks; for (auto &limb : limbs) { diff --git a/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot.cpp b/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot.cpp index 601125cc..9cc52666 100644 --- a/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot.cpp +++ b/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot.cpp @@ -140,7 +140,7 @@ void HumanoidRobot::init() { std::vector(7, 1.0)); - ik_solver_ = std::make_shared("/home/lgv/cmvr/cmvr-es/model/xiaoyan_description/dual_arm.urdf", + ik_solver_ = std::make_shared("/home/cmvr/Projects/cmvr-es/model/xiaoyan_description/dual_arm.urdf", "PELVIS_S", "R_WRIST_R_S", "R_FINGER_TIP_FIXED"); diff --git a/cmvr-es/main.cpp b/cmvr-es/main.cpp index b659f177..24f189a5 100644 --- a/cmvr-es/main.cpp +++ b/cmvr-es/main.cpp @@ -29,7 +29,8 @@ int main(int argc, char* argv[]) { } ServerRunner runner; - runner.start(); + const XmlNode config(config_path); + runner.start(config); runner.join(); google::ShutdownGoogleLogging(); return 0; diff --git a/cmvr-es/service/grpc/include/server_runner.h b/cmvr-es/service/grpc/include/server_runner.h index e148c811..7ad0706e 100644 --- a/cmvr-es/service/grpc/include/server_runner.h +++ b/cmvr-es/service/grpc/include/server_runner.h @@ -18,7 +18,7 @@ namespace cmvr::service { class ServerRunner { public: ServerRunner(); - ServerRunner(const cmvr::config::ServerConfig& config); + explicit ServerRunner(XmlNode cfg); ~ServerRunner(); ServerRunner(const ServerRunner&) = delete; @@ -26,7 +26,7 @@ namespace cmvr::service { ServerRunner(ServerRunner&&) = delete; ServerRunner& operator=(ServerRunner&&) = delete; - void start(); + void start(XmlNode cfg); void stop(); void join(); @@ -41,7 +41,8 @@ namespace cmvr::service { std::condition_variable cv_; std::thread worker_; - cmvr::config::ServerConfig cfg_; + XmlNode cfg_; + cmvr::config::ServerConfig servercfg_; std::unique_ptr server_; std::string address_; diff --git a/cmvr-es/service/grpc/src/server_runner.cpp b/cmvr-es/service/grpc/src/server_runner.cpp index ec04091a..ba3d0ce1 100644 --- a/cmvr-es/service/grpc/src/server_runner.cpp +++ b/cmvr-es/service/grpc/src/server_runner.cpp @@ -25,21 +25,23 @@ using namespace cmvr::service; ServerRunner::ServerRunner() = default; -ServerRunner::ServerRunner(const cmvr::config::ServerConfig& config):cfg_(config) -{ - // start(); +ServerRunner::ServerRunner(XmlNode cfg) { + start(std::move(cfg)); } + ServerRunner::~ServerRunner() { stop(); join(); } -void ServerRunner::start() { +void ServerRunner::start(XmlNode cfg) { std::unique_lock lk(mtx_); if (worker_.joinable()) { throw std::runtime_error("ServerRunner already started"); } + + cfg_ = std::move(cfg); stop_requested_ = false; started_ = false; start_failed_ = false; @@ -101,9 +103,11 @@ void ServerRunner::threadMain() { std::call_once(reflection_once, []() { grpc::reflection::InitProtoReflectionServerBuilderPlugin(); }); - DeviceManager::getInstance(); - std::string port = cfg_.grpc_server().port(); + auto dmgr_cfg = cfg_.getChild("DeviceManager"); + DeviceManager::getInstance(dmgr_cfg); + + std::string port = servercfg_.grpc_server().port(); std::string local_address = "0.0.0.0:" + port; auto camera_service = std::make_unique(); From f6817c7b1c1aa3cd42307fa8dcfde3f17f5168bd Mon Sep 17 00:00:00 2001 From: tankaitao <1767759995@qq.com> Date: Wed, 17 Jun 2026 14:50:37 +0800 Subject: [PATCH 4/9] agv --- CMakeLists.txt | 2 + .../config/agv_config/agv_config.pb.txt | 30 + .../config_helper/include/config_helper.h | 7 + .../config_helper/include/config_setting.h | 2 + .../config_helper/src/config_setting.cpp | 4 + cmvr-es/device_manager/CMakeLists.txt | 1 + .../device_manager/include/device_factory.h | 2 +- .../device_manager/include/device_manager.h | 2 +- cmvr-es/device_manager/src/device_factory.cpp | 21 +- cmvr-es/device_manager/src/device_manager.cpp | 43 +- cmvr-es/devices/CMakeLists.txt | 1 + cmvr-es/devices/agv/CMakeLists.txt | 2 + cmvr-es/devices/agv/abstract_agv.h | 450 +++- .../devices/agv/agv_src1100/CMakeLists.txt | 20 + .../agv/agv_src1100/include/agv_src1100.h | 88 + .../agv/agv_src1100/src/agv_src1100.cpp | 1830 +++++++++++++++++ cmvr-es/service/CMakeLists.txt | 1 + .../service/grpc/include/grpc_agv_service.h | 131 ++ cmvr-es/service/grpc/src/grpc_agv_service.cpp | 708 +++++++ cmvr-es/service/grpc/src/server_runner.cpp | 3 + protos/cmvr/api/agv_command.proto | 424 ++++ protos/cmvr/api/agv_service.proto | 60 + .../cmvr/config/agv_config/agv_config.proto | 20 + 23 files changed, 3800 insertions(+), 52 deletions(-) create mode 100644 cmvr-es/common/config/agv_config/agv_config.pb.txt create mode 100644 cmvr-es/devices/agv/CMakeLists.txt create mode 100644 cmvr-es/devices/agv/agv_src1100/CMakeLists.txt create mode 100644 cmvr-es/devices/agv/agv_src1100/include/agv_src1100.h create mode 100644 cmvr-es/devices/agv/agv_src1100/src/agv_src1100.cpp create mode 100644 cmvr-es/service/grpc/include/grpc_agv_service.h create mode 100644 cmvr-es/service/grpc/src/grpc_agv_service.cpp create mode 100644 protos/cmvr/api/agv_command.proto create mode 100644 protos/cmvr/api/agv_service.proto create mode 100644 protos/cmvr/config/agv_config/agv_config.proto diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e100d3a..77cc732c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,6 +122,8 @@ target_link_libraries(cmvr_es PRIVATE cmvr_es::device::humanoid_robot cmvr_es::common cmvr_es::applications + cmvr_es::device::agv_src1100 + ) install(TARGETS cmvr_es RUNTIME DESTINATION bin) diff --git a/cmvr-es/common/config/agv_config/agv_config.pb.txt b/cmvr-es/common/config/agv_config/agv_config.pb.txt new file mode 100644 index 00000000..f62d19f2 --- /dev/null +++ b/cmvr-es/common/config/agv_config/agv_config.pb.txt @@ -0,0 +1,30 @@ +src1100_agvs { + id: "agv_src1100" + ip: "192.168.192.5" + port_status: 19301 + port_control: 19301 + port_nav: 19301 + port_config: 19301 + port_other: 19301 + port_push: 19301 + + + + enable: true +} + + +src1100_agvs { + id: "agv_src2200" + ip: "192.168.192.5" + port_status: 19204 + port_control: 19205 + port_nav: 19206 + port_config: 19207 + port_other: 19210 + port_push: 19301 + + + + enable: true +} diff --git a/cmvr-es/common/utils/config_helper/include/config_helper.h b/cmvr-es/common/utils/config_helper/include/config_helper.h index 445af379..e7ae3404 100644 --- a/cmvr-es/common/utils/config_helper/include/config_helper.h +++ b/cmvr-es/common/utils/config_helper/include/config_helper.h @@ -10,6 +10,7 @@ #include "cmvr/config/microphone_config/microphone_config.pb.h" #include "cmvr/config/speaker_config/speaker_conifg.pb.h" #include "cmvr/config/touch_screen_app_config/touch_screen_app_config.pb.h" +#include "cmvr/config/agv_config/agv_config.grpc.pb.h" #include "cmvr/config/biohead_config/biohead_config.pb.h" #include "cmvr/config/robot_config/robot_config.pb.h" #include "cmvr/config/server_config/server_config.pb.h" @@ -85,6 +86,12 @@ namespace cmvr return SET_CONFIG(config, touch_screen_app_config_file); } + static bool getAgvConfig(config::AGVConfig& config) + { + return GET_CONFIG(agv_config_file, config); + } + + static bool getHeadConfig(config::HeadConfig& config) { return GET_CONFIG(biohead_config_file, config); diff --git a/cmvr-es/common/utils/config_helper/include/config_setting.h b/cmvr-es/common/utils/config_helper/include/config_setting.h index caedf533..82c22b5f 100644 --- a/cmvr-es/common/utils/config_helper/include/config_setting.h +++ b/cmvr-es/common/utils/config_helper/include/config_setting.h @@ -6,6 +6,8 @@ DECLARE_string(dexhand_config_file); DECLARE_string(microphone_config_file); DECLARE_string(speaker_config_file); DECLARE_string(touch_screen_app_config_file); +DECLARE_string(agv_config_file); + DECLARE_string(biohead_config_file); DECLARE_string(robot_config_file); DECLARE_string(server_config_file); diff --git a/cmvr-es/common/utils/config_helper/src/config_setting.cpp b/cmvr-es/common/utils/config_helper/src/config_setting.cpp index 528819f5..041b4885 100644 --- a/cmvr-es/common/utils/config_helper/src/config_setting.cpp +++ b/cmvr-es/common/utils/config_helper/src/config_setting.cpp @@ -89,6 +89,10 @@ DEFINE_string(touch_screen_app_config_file, basePath() + "touch_screen_app_config/touch_screen_app_config.pb.txt", "The configuration file for TouchScreenApp"); +DEFINE_string(agv_config_file, + basePath() + "agv_config/agv_config.pb.txt", + "AGV config file"); + DEFINE_string(biohead_config_file, basePath() + "biohead_config/biohead_config.pb.txt", "The configuration file for BioHead"); diff --git a/cmvr-es/device_manager/CMakeLists.txt b/cmvr-es/device_manager/CMakeLists.txt index 0b97b456..d74d86c3 100644 --- a/cmvr-es/device_manager/CMakeLists.txt +++ b/cmvr-es/device_manager/CMakeLists.txt @@ -15,6 +15,7 @@ target_link_libraries(device_manager PRIVATE cmvr_es::device::rh56dftp_dexhand cmvr_es::device::px_6ax_gen3 cmvr::device::head_esp32 + cmvr_es::device::agv_src1100 osqp cmvr_es::device::humanoid_robot # cmvr_es::device::aubo_robot diff --git a/cmvr-es/device_manager/include/device_factory.h b/cmvr-es/device_manager/include/device_factory.h index 1fcce739..b46e3495 100644 --- a/cmvr-es/device_manager/include/device_factory.h +++ b/cmvr-es/device_manager/include/device_factory.h @@ -28,7 +28,7 @@ namespace cmvr::device { template std::shared_ptr createFromConfig(const ConfigType& cfg); private: - std::shared_ptr create_agv_(const XmlNode& cfg); + std::shared_ptr create_agv_(const XmlNode& cfg); std::shared_ptr create_battery_(const XmlNode& cfg); std::shared_ptr create_camera_(const XmlNode& cfg); std::shared_ptr create_dexhand_(const XmlNode& cfg); diff --git a/cmvr-es/device_manager/include/device_manager.h b/cmvr-es/device_manager/include/device_manager.h index ee782e5d..8f54823b 100644 --- a/cmvr-es/device_manager/include/device_manager.h +++ b/cmvr-es/device_manager/include/device_manager.h @@ -16,7 +16,7 @@ namespace cmvr::device { using DeviceVariant = std::variant< - std::shared_ptr, + std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, diff --git a/cmvr-es/device_manager/src/device_factory.cpp b/cmvr-es/device_manager/src/device_factory.cpp index 2c136ac5..84262f35 100644 --- a/cmvr-es/device_manager/src/device_factory.cpp +++ b/cmvr-es/device_manager/src/device_factory.cpp @@ -15,12 +15,13 @@ //#include "robot/ti5_robot/ti5_robot.h" #include "cmvr/api/system_command.pb.h" #include "data_center/include/motors_info.h" +#include "devices/agv/agv_src1100/include/agv_src1100.h" //#include "devices/robot/aubo_robot/include/aubo_robot.h" using namespace std; using namespace cmvr::device; -template std::shared_ptr DeviceFactory::create(const XmlNode&); +template std::shared_ptr DeviceFactory::create(const XmlNode&); template std::shared_ptr DeviceFactory::create(const XmlNode&); template std::shared_ptr DeviceFactory::create(const XmlNode&); template std::shared_ptr DeviceFactory::create(const XmlNode&); @@ -32,7 +33,7 @@ template std::shared_ptr DeviceFactory::create template std::shared_ptr DeviceFactory::create(const XmlNode& cfg) { - if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) { return create_agv_(cfg); } else if constexpr (std::is_same_v) { return create_battery_(cfg); @@ -57,7 +58,7 @@ std::shared_ptr DeviceFactory::create(const XmlNode& cfg) { } } -std::shared_ptr DeviceFactory::create_agv_(const XmlNode& cfg) { +std::shared_ptr DeviceFactory::create_agv_(const XmlNode& cfg) { return nullptr; } @@ -184,6 +185,7 @@ template std::shared_ptr DeviceFactory::createFromConfig DeviceFactory::createFromConfig(const config::PX6AXGen3& cfg); template std::shared_ptr DeviceFactory::createFromConfig(const config::FFMpegMicroPhoneConfig& cfg); template std::shared_ptr DeviceFactory::createFromConfig(const config::FFMpegSpeakerConfig& cfg); +template std::shared_ptr DeviceFactory::createFromConfig(const config::AGVsrc1100Config& cfg); template std::shared_ptr DeviceFactory::createFromConfig(const config::BioHeadConfig& cfg); template std::shared_ptr DeviceFactory::createFromConfig(const config::HumanRobotConfig& cfg); template std::shared_ptr DeviceFactory::createFromConfig(const config::EthercatRobotConfig& cfg); @@ -280,6 +282,18 @@ std::shared_ptr DeviceFactory::createFromConfig(const ConfigType& cf throw runtime_error("[DeviceFactory]: Unsupported microphone device type"); } } + else if constexpr (std::is_same_v) + { + if constexpr (std::is_same_v) + { + return std::make_shared(cfg); + } + else + { + LOG(ERROR) << "[DeviceFactory]: Unsupported agv device type "; + throw runtime_error("[DeviceFactory]: Unsupported agv device type"); + } + } else { LOG(ERROR) << "[DeviceFactory]: Unsupported device type"; throw runtime_error("[DeviceFactory]: Unsupported device type"); @@ -289,4 +303,3 @@ std::shared_ptr DeviceFactory::createFromConfig(const ConfigType& cf return nullptr; } } - diff --git a/cmvr-es/device_manager/src/device_manager.cpp b/cmvr-es/device_manager/src/device_manager.cpp index 67c06de0..581a0aa6 100644 --- a/cmvr-es/device_manager/src/device_manager.cpp +++ b/cmvr-es/device_manager/src/device_manager.cpp @@ -9,7 +9,7 @@ using namespace std; using namespace cmvr::device; using namespace cmvr::device; -template std::shared_ptr DeviceManager::getDevice(const std::string& device_id); +template std::shared_ptr DeviceManager::getDevice(const std::string& device_id); template std::shared_ptr DeviceManager::getDevice(const std::string& device_id); template std::shared_ptr DeviceManager::getDevice(const std::string& device_id); template std::shared_ptr DeviceManager::getDevice(const std::string& device_id); @@ -152,7 +152,7 @@ void DeviceManager::getDeviceList(std::list> string dev_type; std::visit([&](const auto& ptr) { using T = std::decay_t; - if constexpr (std::is_same_v>) { + if constexpr (std::is_same_v>) { dev_type = "AGV"; } else if constexpr (std::is_same_v>) { dev_type = "Battery"; @@ -223,12 +223,12 @@ void DeviceManager::init_devices_() { LOG(ERROR) << "[DeviceManager]: Duplicate AGV Device ID" << id; throw runtime_error("[DeviceManager]: Duplicate AGV Device ID" + id); } - auto device = dev_factory_->create(node); + auto device = dev_factory_->create(node); if (device == nullptr) { throw std::runtime_error("[DeviceManager]: Factory returned nullptr for device ID: " + id); } devices_[id] = device; - std::get>(devices_[id])->init(); + std::get>(devices_[id])->init(); LOG(INFO) << "[DeviceManager]: Init AGV " << id << " Success"; } @@ -312,24 +312,6 @@ void DeviceManager::init_devices_() { LOG(INFO) << "[DeviceManager]: Init Robot " << id << " Success"; } - // EYOU的先用新版的 - config::RobotConfig robot_config; - ConfigHelper::getRobotConfig(robot_config); - for (int i = 0; i < robot_config.ethercat_robots().size(); i++) { - auto robot = robot_config.ethercat_robots(i); - if (!robot.enable()) - continue; - auto id = robot.id(); - if (devices_.count(id)) { - LOG(ERROR) << "[DeviceManager]: Duplicate Robot Device ID" << id; - throw runtime_error("[DeviceManager]: Duplicate Robot Device ID" + id); - } - std::cout << "DexHand[" << i << "]: " << id << std::endl; - auto device = dev_factory_->createFromConfig(robot); - devices_[id] = device; - std::get>(devices_[id])->init(); - } - config::MicroPhoneConfig micro_phone_config; ConfigHelper::getMicroPhonesConfig(micro_phone_config); for (int i = 0; i < micro_phone_config.ffmpeg_microphones().size(); i++) { @@ -380,6 +362,23 @@ void DeviceManager::init_devices_() { devices_[id] = device; std::get>(devices_[id])->init(); } + config::AGVConfig agv_config; + ConfigHelper::getAgvConfig(agv_config); + + for (int i = 0; i < agv_config.src1100_agvs().size(); i++) { + auto agv = agv_config.src1100_agvs(i); + if (!agv.enable()) + continue; + auto id = agv.id(); + if (devices_.count(id)) { + LOG(ERROR) << "[DeviceManager]: Duplicate agv Device ID" << id; + throw runtime_error("[DeviceManager]: Duplicate agv Device ID" + id); + } + std::cout << "AgvSrc1100[" << i << "]: " << id << std::endl; + auto device = dev_factory_->createFromConfig(agv); + devices_[id] = device; + std::get>(devices_[id])->init(); + } } catch (const exception& e) { LOG(ERROR) << e.what(); diff --git a/cmvr-es/devices/CMakeLists.txt b/cmvr-es/devices/CMakeLists.txt index 41c64db3..21191941 100644 --- a/cmvr-es/devices/CMakeLists.txt +++ b/cmvr-es/devices/CMakeLists.txt @@ -6,3 +6,4 @@ add_subdirectory(biohead) add_subdirectory(robot) add_subdirectory(canbus) add_subdirectory(motor) +add_subdirectory(agv) diff --git a/cmvr-es/devices/agv/CMakeLists.txt b/cmvr-es/devices/agv/CMakeLists.txt new file mode 100644 index 00000000..0d8b4bb9 --- /dev/null +++ b/cmvr-es/devices/agv/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(agv_src1100) + diff --git a/cmvr-es/devices/agv/abstract_agv.h b/cmvr-es/devices/agv/abstract_agv.h index a838fa89..da3c1ec9 100644 --- a/cmvr-es/devices/agv/abstract_agv.h +++ b/cmvr-es/devices/agv/abstract_agv.h @@ -1,36 +1,438 @@ // -// Created by xtkuang on 2025/5/8. +// Created by xtkuang on 2025/5/6. // - #ifndef CMVR_ES_ABSTRACT_AGV_H #define CMVR_ES_ABSTRACT_AGV_H #pragma once -#include "../abstract_device.h" +#include +#include +#include +#include +#include +#include +#include +#include -namespace cmvr::device{ - class AbstractAGV: public AbstractDevice { - public: - explicit AbstractAGV(const XmlNode &config): AbstractDevice(config) {}; - ~AbstractAGV() override=default; +#include "devices/abstract_device.h" - virtual void getState(AGVState &state) {} +// 注意:这里只include,不使用 +#include "cmvr/config/agv_config/agv_config.pb.h" - // navigation - virtual void eStop() {} - virtual void goHome() {} - virtual void moveto(math::Pose2d &location, double speed_ratio) {} - virtual void setVelocity(math::Vec3 linear, math::Vec3 angular) {} +namespace cmvr::device { - // map - virtual void initMap(float resolution, int width, int height) {} - virtual void updateMap() {} - virtual void saveMap(const std::string& file_path) {} - virtual void loadMap(const std::string& file_path) {} - - protected: - AGVState state_; +class AbstractAgv : public AbstractDevice { +public: + enum class Status { + CREATED, + INITIALIZED, + RUNNING, + PAUSED, + STOPPED, + FAULT }; -} + //查询机器人信息 + struct AgvStatusInfo { + std::string id; + std::string vehicle_id; + std::string robot_note; + std::string version; + std::string model; + std::string dsp_version; + std::string gyro_version; + std::string map_version; + std::string model_version; + std::string netprotocol_version; + std::string modbus_version; + std::string current_map; + std::string current_map_md5; + std::string model_md5; + std::string ssid; + int rssi = 0; + std::string ap_addr; + std::string current_ip; + std::string mac; + std::string echoid_type; + std::string echoid; + int ret_code = 0; + std::string err_msg; -#endif //CMVR_ES_ABSTRACT_AGV_H \ No newline at end of file + bool isValid() const { + return !id.empty() && ret_code == 0; + } + }; + + + // 电池状态信息 + struct BatteryStatus + { + double battery_level = 0.0; + double battery_temp = 0.0; + bool charging = false; + double voltage = 0.0; + double current = 0.0; + double max_charge_voltage = -1.0; + double max_charge_current = -1.0; + bool manual_charge = false; + bool auto_charge = false; + int32_t battery_cycle = 0; + std::string battery_user_data; + std::string extra; + int32_t ret_code = 0; + std::string create_on; + std::string err_msg; + }; + + + // 机器人位置信息 + struct RobotLocation + { + double x = 0.0; + double y = 0.0; + double angle = 0.0; + double confidence = 0.0; + std::string current_station; + std::string last_station; + int32_t loc_method = 0; + int32_t ret_code = 0; + std::string create_on; + std::string err_msg; + }; + + // 地图下载结果 + struct DownloadMapResult + { + std::string map_name; + std::string map_content; + int32_t ret_code = 0; + std::string create_on; + std::string err_msg; + }; + + + + // 单地图文件信息 + struct MapFileInfo + { + std::string name; + std::string modified; + int64_t size = 0; + }; + + // 地图整体状态 + struct MapStatus + { + std::string current_map; + std::string current_map_md5; + std::vector maps; + std::vector map_files_info; + int32_t ret_code = 0; + std::string create_on; + std::string err_msg; + }; + + + // 上传地图结果 + struct UploadMapResult + { + int32_t ret_code = 0; + std::string create_on; + std::string err_msg; + }; + + // 抢占控制权返回结果 + struct LockResult + { + int32_t ret_code = 0; + std::string create_on; + std::string err_msg; + }; + + + // 当前控制权信息 + struct CurrentLockStatus + { + bool locked = false; + std::string ip; + int32_t port = 0; + uint8_t type = 0; + std::string nick_name; + int64_t time_t = 0; + std::string desc; + int32_t ret_code = 0; + std::string create_on; + std::string err_msg; + }; + + + //==================== 新增:运动控制结构体 ==================== + // 开环运动下发参数结构体 + struct MotionCtrlReq + { + double vx = 0.0; + double vy = 0.0; + double w = 0.0; + double steer = 0.0; + double real_steer = 0.0; + int64_t duration = -1; // -1 代表缺省,使用设备默认时长 + }; + + // 运动控制返回结果结构体 + struct MotionCtrlRes + { + int ret_code = 0; + std::string create_on; + std::string err_msg; + }; + + + // 切换地图入参 + struct LoadMapReq + { + std::string map_name; + }; + + // 切换地图返回结果 + struct LoadMapRes + { + int ret_code = 0; + std::string create_on; + std::string err_msg; + }; + + // 查询地图载入状态返回 + struct QueryLoadMapStatusRes + { + int32_t loadmap_status = 0; //0失败 1成功 2载入中 + int ret_code = 0; + std::string create_on; + std::string err_msg; + }; + + + // 单个站点信息 + struct StationItem + { + std::string id; + std::string type; + double x = 0.0; + double y = 0.0; + double r = 0.0; + std::string desc; + std::string executor; + std::string prepoint; + std::string recfile; + bool spin = false; + bool use_down_pgv = false; + }; + + // 查询站点列表返回结果 + struct QueryStationRes + { + std::vector stations; + int ret_code = 0; + std::string create_on; + std::string err_msg; + }; + + + // 单段导航任务 + struct MoveTaskItem + { + std::string task_id; + std::string source_id; + std::string id; + std::string operation; + double jack_height = 0.0; + }; + + // 指定路径导航入参 + struct GoTargetListReq + { + std::vector move_task_list; + }; + + // 指定路径导航返回 + struct GoTargetListRes + { + int ret_code = 0; + std::string create_on; + std::string err_msg; + }; + + + // 导航料箱信息 + struct NavContainerItem + { + std::string container_name; + std::string desc; + std::string goods_id; + bool has_goods = false; + }; + + // 1020 查询当前导航入参 + struct RobotStatusTaskReq + { + bool simple = false; + }; + + // 1020 查询当前导航出参 + struct RobotStatusTaskRes + { + int task_status = 0; + int task_type = 0; + std::string target_id; + std::vector target_point; + std::vector finished_path; + std::vector unfinished_path; + std::string move_status_info; + std::vector containers; + + int ret_code = 0; + std::string create_on; + std::string err_msg; + }; + + + + + + + using AgvState = AgvStatusInfo; + +public: + AbstractAgv() = default; + ~AbstractAgv() override = default; + + AbstractAgv(const AbstractAgv&) = delete; + AbstractAgv& operator=(const AbstractAgv&) = delete; + AbstractAgv(AbstractAgv&&) = default; + AbstractAgv& operator=(AbstractAgv&&) = default; + + // 纯虚接口 + virtual Status state() const = 0; + virtual std::string lastError() const = 0; + + + void start() override = 0; + void stop() override = 0; + void update() override = 0; + + virtual void getState(AgvState& state) { + state = AgvState{}; + getStatusInfo(state); + } + + virtual void getStatusInfo(AgvStatusInfo& info) = 0; + + // 查询电池状态 + virtual void getBatteryStatus(BatteryStatus& info, bool simple = false) = 0; + + // 查询机器人位置 + virtual void getRobotLocation(RobotLocation& info) = 0; + + // 下载地图 + virtual void downloadMap(DownloadMapResult& info, const std::string& map_name) = 0; + + + // 查询地图状态 + virtual void getMapStatus(MapStatus& info) = 0; + + // 上传地图 + virtual void uploadMap(UploadMapResult& info, const std::string& map_json) = 0; + + /** + * @brief 抢占机器人控制权 + * @param info 出参,抢占结果 + * @param nick_name 入参,抢占者名称 + */ + virtual void lockRobotControl(LockResult& info, const std::string& nick_name) = 0; + + + + /** + * @brief 下发开环速度运动指令 命令码2010(0x07DA),使用控制端口sock_control_ + * @param res 出参 运动指令返回结果 + * @param req 入参 vx/vy/w/steer/real_steer/duration运动参数 + * @note 下发后会强制中断当前自动导航任务;多舵轮设备仅vx/vy/w生效 + */ + virtual void robotMotionControl(MotionCtrlRes& res, const MotionCtrlReq& req) = 0; + + /** + * @brief 切换载入地图 命令码2022(0x07E6),控制端口sock_control_ + * @param res 出参 执行结果 + * @param req 入参 目标地图名称 + */ + virtual void robotLoadMap(LoadMapRes& res, const LoadMapReq& req) = 0; + + + + /** + * @brief 1020(0x03FC) robot_status_task_req 查询当前导航状态 状态端口 sock_status_ + * @param res 输出导航全量状态 + * @param req simple=true 仅返回task_status + */ + virtual void robotStatusTask(RobotStatusTaskRes& res, const RobotStatusTaskReq& req) = 0; + + + + virtual void move(double vx, double vy, double vw) { + throw std::logic_error("move not implemented"); + } + + virtual void stopMove() { + throw std::logic_error("stopMove not implemented"); + } + + /** + * @brief 查询当前机器人控制权持有者 + * @param info 出参 控制权信息 + */ + virtual void getCurrentLockStatus(CurrentLockStatus& info) = 0; + + /** + * @brief 查询地图载入状态 命令码1022(0x03FE),状态端口sock_status_ + * @param res 出参 地图加载状态结果 + * @note loadmap_status=2载入中禁止重定位 + */ + virtual void queryLoadMapStatus(QueryLoadMapStatusRes& res) = 0; + + + /** + * @brief 查询当前地图全部站点信息 命令码1301(0x0515),状态端口sock_status_ + * @param res 出参 站点数组与执行结果 + */ + virtual void queryStationList(QueryStationRes& res) = 0; + + + /** + * @brief 指定路径导航 命令码3066(0x0BFA),控制端口sock_control_ + * @param res 出参 下发任务结果 + * @param req 入参 多段站点导航序列 + * @note task_id、source_id、id 必填;任务串行追加执行 + */ + virtual void robotGoTargetList(GoTargetListRes& res, const GoTargetListReq& req) = 0; + + + + + + +protected: + std::string id_; + std::string ip_; + // ======================== + // 多端口配置(对应 proto) + // ======================== + int port_status_{0}; // 19204 状态查询 + int port_control_{0}; // 19205 控制API + int port_nav_{0}; // 19206 导航API + int port_config_{0}; // 19207 配置API + int port_other_{0}; // 19210 其他API + int port_push_{0}; // 19301 推送 + bool enable_{false}; +}; + +} // namespace cmvr::device + +#endif // CMVR_ES_ABSTRACT_AGV_H \ No newline at end of file diff --git a/cmvr-es/devices/agv/agv_src1100/CMakeLists.txt b/cmvr-es/devices/agv/agv_src1100/CMakeLists.txt new file mode 100644 index 00000000..f18e7907 --- /dev/null +++ b/cmvr-es/devices/agv/agv_src1100/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(agv_src1100 SHARED src/agv_src1100.cpp) + +target_include_directories(agv_src1100 PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${JSONCPP_INCLUDE_DIRS} +) + +add_library(cmvr_es::device::agv_src1100 ALIAS agv_src1100) + +# 关键修改:PRIVATE -> PUBLIC +target_link_libraries(agv_src1100 PUBLIC + jsoncpp + cmvr_es::common + cmvr_es::proto + glog + pthread +) + +install(TARGETS agv_src1100 LIBRARY DESTINATION lib) \ No newline at end of file diff --git a/cmvr-es/devices/agv/agv_src1100/include/agv_src1100.h b/cmvr-es/devices/agv/agv_src1100/include/agv_src1100.h new file mode 100644 index 00000000..847dcfed --- /dev/null +++ b/cmvr-es/devices/agv/agv_src1100/include/agv_src1100.h @@ -0,0 +1,88 @@ +// +// Created by linbo on 2025/6/20. +// +#ifndef AGV_SRC1100_H +#define AGV_SRC1100_H +#pragma once + +#include "./agv/abstract_agv.h" +#include +#include +#include +#include "nlohmann/json.hpp" +using json = nlohmann::json; +namespace cmvr::device { + + class AgvSrc1100 final : public AbstractAgv { + public: + explicit AgvSrc1100(const XmlNode& cfg); + explicit AgvSrc1100(const config::AGVsrc1100Config& cfg); + ~AgvSrc1100() override; + + Status state() const override; + std::string lastError() const override; + + void start() override; + void stop() override; + void update() override; + + void getStatusInfo(AgvStatusInfo& info) override; + void getBatteryStatus(BatteryStatus& info, bool simple = false) override; + void getRobotLocation(RobotLocation& info) override; + void downloadMap(DownloadMapResult& info, const std::string& map_name) override; + void getMapStatus(MapStatus& info) override; + void uploadMap(UploadMapResult& info, const std::string& map_json) override; + void lockRobotControl(LockResult& info, const std::string& nick_name) override; + void getCurrentLockStatus(CurrentLockStatus& info) override; + + void robotMotionControl(MotionCtrlRes& res, const MotionCtrlReq& req) override; + + void robotLoadMap(LoadMapRes& res, const LoadMapReq& req) override; + + void queryLoadMapStatus(QueryLoadMapStatusRes& res) override; + void queryStationList(QueryStationRes& res) override; + + void robotGoTargetList(GoTargetListRes& res, const GoTargetListReq& req) override; + + + void robotStatusTask(RobotStatusTaskRes& res, const RobotStatusTaskReq& req) override; + + + + + + + + private: + void asyncConnect(int& sock, int port); + bool sendAndRecv(int sock, const uint8_t* header); + void flushSocket(int sock); + void setSocketTimeout(int sock, int timeout_ms); + + + + // 辅助函数:将JSON字符串解析为Json::Value,同时做错误处理 + bool parseJson(const std::string& json_str, json& root, std::string& err_msg); + + mutable std::mutex mutex_; + std::atomic status_{Status::CREATED}; + std::string last_error_; + std::string current_json_; + + + + // 多socket + int sock_status_ = -1; + int sock_control_ = -1; + int sock_nav_ = -1; + int sock_config_ = -1; + int sock_other_ = -1; + int sock_push_ = -1; + + std::vector connect_threads_; + mutable std::mutex connect_mutex_; + }; + +} // namespace cmvr::device + +#endif // AGV_SRC1100_H \ No newline at end of file diff --git a/cmvr-es/devices/agv/agv_src1100/src/agv_src1100.cpp b/cmvr-es/devices/agv/agv_src1100/src/agv_src1100.cpp new file mode 100644 index 00000000..f657eb1a --- /dev/null +++ b/cmvr-es/devices/agv/agv_src1100/src/agv_src1100.cpp @@ -0,0 +1,1830 @@ +#include "../include/agv_src1100.h" +#include +#include +#include +#include +#include +#include +#include +#include +// #include "json/json.h" +#include "nlohmann/json.hpp" +using json = nlohmann::json; +namespace cmvr::device { + +// ====================================================================== +// 构造函数(多端口版本) +// ====================================================================== + + +AgvSrc1100::AgvSrc1100(const config::AGVsrc1100Config& cfg) : AbstractAgv() { + LOG(INFO) << "[AgvSrc1100 Create] this_addr=" << this + << " cfg_id=" << cfg.id() + << " cfg_ip=" << cfg.ip() + << " cfg_port_config=" << cfg.port_config(); + id_ = cfg.id(); + ip_ = cfg.ip(); + enable_ = cfg.enable(); + + port_status_ = cfg.port_status(); + port_control_ = cfg.port_control(); + port_nav_ = cfg.port_nav(); + port_config_ = cfg.port_config(); + port_other_ = cfg.port_other(); + port_push_ = cfg.port_push(); + + if (enable_) { + connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_status_), port_status_); + connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_control_), port_control_); + connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_nav_), port_nav_); + connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_config_), port_config_); + connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_other_), port_other_); + connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_push_), port_push_); + } +} + +AgvSrc1100::~AgvSrc1100() { + stop(); + for (auto& t : connect_threads_) { + if (t.joinable()) t.join(); + } +} + +AbstractAgv::Status AgvSrc1100::state() const { + return status_; +} + +std::string AgvSrc1100::lastError() const { + std::lock_guard lock(mutex_); + return last_error_; +} + +// ====================================================================== +// 通用异步连接 +// ====================================================================== +void AgvSrc1100::asyncConnect(int& sock, int port) { + int s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) { + std::lock_guard lock(mutex_); + last_error_ = "socket create failed"; + status_ = Status::FAULT; + return; + } + + struct sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, ip_.c_str(), &addr.sin_addr); + + if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + close(s); + std::lock_guard lock(mutex_); + last_error_ = "connect failed port:" + std::to_string(port); + status_ = Status::FAULT; + return; + } + + { + std::lock_guard lock(mutex_); + sock = s; + status_ = Status::INITIALIZED; + } +} + +void AgvSrc1100::start() { + status_ = Status::RUNNING; +} + +void AgvSrc1100::stop() { + auto closeSocket = [](int& s) { + if (s >= 0) { close(s); s = -1; } + }; + closeSocket(sock_status_); + closeSocket(sock_control_); + closeSocket(sock_nav_); + closeSocket(sock_config_); + closeSocket(sock_other_); + closeSocket(sock_push_); + status_ = Status::STOPPED; +} + +void AgvSrc1100::update() {} + +void AgvSrc1100::flushSocket(int sock) { + uint8_t tmp[128]; + ssize_t rlen; + while ((rlen = recv(sock, tmp, sizeof(tmp), MSG_DONTWAIT)) > 0); +} + +void AgvSrc1100::setSocketTimeout(int sock, int timeout_ms) { + struct timeval tv; + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); +} + +// ====================================================================== +// 增强版收发:确保完整接收所有数据帧 +// ====================================================================== + bool AgvSrc1100::sendAndRecv(int sock, const uint8_t* header) { + std::lock_guard lock(mutex_); + + if (sock < 0) { + last_error_ = "socket not connected"; + return false; + } + + flushSocket(sock); + + // 发送16字节请求包头 + if (send(sock, header, 16, MSG_NOSIGNAL) != 16) { + last_error_ = "send header failed"; + return false; + } + + // 整体接收超时 1000ms + setSocketTimeout(sock, 100); + + const int RECV_BUF_LEN = 16384; + uint8_t recvBuf[RECV_BUF_LEN] = {0}; + int totalRecv = 0; + + // 循环接收,直到读空 or 缓冲区满 + while (totalRecv < RECV_BUF_LEN) + { + int ret = recv(sock, recvBuf + totalRecv, RECV_BUF_LEN - totalRecv, 0); + if (ret <= 0) break; // 读空/超时,退出 + + totalRecv += ret; + } + + setSocketTimeout(sock, 0); + + if (totalRecv <= 0) { + last_error_ = "no data received"; + return false; + } + + std::string rawData(reinterpret_cast(recvBuf), totalRecv); + size_t jsonStart = rawData.find('{'); + size_t jsonEnd = rawData.rfind('}'); + + // 兜底:如果没找到结尾 },手动补全,修复截断报错 + if (jsonStart == std::string::npos) + { + last_error_ = "can not find '{'"; + return false; + } + if (jsonEnd == std::string::npos) + { + // 找不到 },截取到末尾并补 } + current_json_ = rawData.substr(jsonStart) + "}"; + } + else + { + current_json_ = rawData.substr(jsonStart, jsonEnd - jsonStart + 1); + } + + + fflush(stdout); + + last_error_ = "ok"; + return true; +} + +// ====================================================================== +// 辅助JSON解析函数 +// ====================================================================== + bool AgvSrc1100::parseJson(const std::string& json_str, json& root, std::string& err_msg) +{ + root.clear(); + try + { + root = json::parse(json_str); + } + catch (json::parse_error& e) + { + err_msg = "JSON parse error: " + std::string(e.what()); + return false; + } + return true; +} + +// =========================================================================== +// 接口1:查询设备信息 → 19204 命令码 1000 (0x03E8) +// =========================================================================== + void AgvSrc1100::getStatusInfo(AgvStatusInfo& info) { + int sock = sock_status_; + if (sock < 0) { + info.ret_code = -1; + info.err_msg = "socket not connected"; + return; + } + + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x03, 0xE8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + if (!sendAndRecv(sock, send_buf)) { + info.ret_code = -2; + info.err_msg = last_error_; + return; + } + + std::string json_str; + { + std::lock_guard lk(mutex_); + json_str = current_json_; + } + + // 兜底修复JSON闭合 + if (!json_str.empty()) + { + if (json_str.front() != '{') json_str = "{" + json_str; + if (json_str.back() != '}') json_str += "}"; + } + + if (json_str.empty()) { + info.ret_code = -4; + info.err_msg = "empty json data"; + return; + } + + json root; + std::string err; + if (!parseJson(json_str, root, err)) { + info.ret_code = -3; + info.err_msg = err; + return; + } + + info.id = root.value("id", ""); + info.vehicle_id = root.value("vehicle_id", ""); + info.version = root.value("version", ""); + info.model = root.value("model", ""); + info.dsp_version = root.value("dsp_version", ""); + info.current_ip = root.value("current_ip", ""); + info.mac = root.value("MAC", ""); + info.rssi = root.value("rssi", 0); + info.ret_code = root.value("ret_code", 0); + info.err_msg = root.value("err_msg", ""); +} + + +// =========================================================================== +// 接口:查询机器人电池状态 → 命令码 1007 (0x03EF) +// =========================================================================== +void AgvSrc1100::getBatteryStatus(BatteryStatus& info, bool simple) +{ + int sock = sock_status_; + if (sock < 0) + { + info.ret_code = -1; + info.err_msg = "socket not connected"; + return; + } + + // 步骤1:构造16字节请求帧 + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x03, 0xEF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + if (!sendAndRecv(sock, send_buf)) + { + info.ret_code = -2; + info.err_msg = last_error_; + return; + } + + // 步骤2:数据拷贝 + JSON 兜底 + 解析 + std::string json_str; + { + std::lock_guard lk(mutex_); + json_str = current_json_; + } + + if (!json_str.empty()) + { + if (json_str.front() != '{') json_str = "{" + json_str; + if (json_str.back() != '}') json_str += "}"; + } + + if (json_str.empty()) + { + info.ret_code = -4; + info.err_msg = "empty json data"; + return; + } + + json root; + std::string err; + if (!parseJson(json_str, root, err)) + { + info.ret_code = -3; + info.err_msg = err; + return; + } + + // 步骤3:字段赋值 + info.battery_level = root.value("battery_level", 0.0); + info.battery_temp = root.value("battery_temp", 0.0); + info.charging = root.value("charging", false); + info.voltage = root.value("voltage", 0.0); + info.current = root.value("current", 0.0); + + info.max_charge_voltage = root.value("max_charge_voltage", -1.0); + info.max_charge_current = root.value("max_charge_current", -1.0); + + info.manual_charge = root.value("manual_charge", false); + info.auto_charge = root.value("auto_charge", false); + info.battery_cycle = root.value("battery_cycle", 0); + + info.battery_user_data = root.value("battery_user_data", ""); + info.extra = root.value("extra", ""); + + info.ret_code = root.value("ret_code", 0); + info.create_on = root.value("create_on", ""); + info.err_msg = root.value("err_msg", ""); +} + + + // =========================================================================== + // 接口:查询机器人位置 → 命令码 1004 (0x03EC) + // =========================================================================== + void AgvSrc1100::getRobotLocation(RobotLocation& info) +{ + int sock = sock_status_; + if (sock < 0) + { + info.ret_code = -1; + info.err_msg = "socket not connected"; + return; + } + + // 步骤1:构造16字节请求帧 + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x03, 0xEC, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + if (!sendAndRecv(sock, send_buf)) + { + info.ret_code = -2; + info.err_msg = last_error_; + return; + } + + // 步骤2:数据拷贝 + JSON兜底 + 解析 + std::string json_str; + { + std::lock_guard lk(mutex_); + json_str = current_json_; + } + + if (!json_str.empty()) + { + if (json_str.front() != '{') json_str = "{" + json_str; + if (json_str.back() != '}') json_str += "}"; + } + + if (json_str.empty()) + { + info.ret_code = -4; + info.err_msg = "empty json data"; + return; + } + + json root; + std::string err; + if (!parseJson(json_str, root, err)) + { + info.ret_code = -3; + info.err_msg = err; + return; + } + + // 步骤3:字段赋值 + info.x = root.value("x", 0.0); + info.y = root.value("y", 0.0); + info.angle = root.value("angle", 0.0); + info.confidence = root.value("confidence", 0.0); + info.current_station = root.value("current_station", ""); + info.last_station = root.value("last_station", ""); + info.loc_method = root.value("loc_method", 0); + info.ret_code = root.value("ret_code", 0); + info.create_on = root.value("create_on", ""); + info.err_msg = root.value("err_msg", ""); +} + + + // =========================================================================== +// 接口:下载地图 命令码 4011(0x0FAB) +// =========================================================================== +void AgvSrc1100::downloadMap(DownloadMapResult& info, const std::string& map_name) +{ + int sock = sock_config_; + if (sock < 0) + { + info.ret_code = -1; + info.err_msg = "socket not connected"; + return; + } + + // 组装请求JSON + std::string req_json = R"({"map_name":")" + map_name + R"("})"; + uint8_t payload_len = static_cast(req_json.size()); + + // 严格对齐协议帧头 + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, payload_len, + 0x0F, 0xAB, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + // 发送帧头 + if (send(sock, send_buf, 16, MSG_NOSIGNAL) != 16) + { + info.ret_code = -2; + info.err_msg = "send frame header failed"; + return; + } + // 发送JSON负载 + if (send(sock, req_json.data(), req_json.size(), MSG_NOSIGNAL) != (ssize_t)req_json.size()) + { + info.ret_code = -2; + info.err_msg = "send payload failed"; + return; + } + + // 增大缓冲区 + 循环接收,确保大数据收完整 + const int RECV_BUF_LEN = 65536; + std::vector recvBuf(RECV_BUF_LEN); + std::string rawData; + int ret = 0; + + setSocketTimeout(sock, 500); // 延长超时适配大地图 + while ((ret = recv(sock, recvBuf.data(), RECV_BUF_LEN, 0)) > 0) + { + rawData.append(reinterpret_cast(recvBuf.data()), ret); + } + setSocketTimeout(sock, 0); + + if (rawData.empty()) + { + info.ret_code = -2; + info.err_msg = "no response data"; + return; + } + + // 截取JSON区间 + size_t jsonStart = rawData.find('{'); + size_t jsonEnd = rawData.rfind('}'); + std::string json_str; + + if (jsonStart == std::string::npos) + { + info.ret_code = -4; + info.err_msg = "json not found"; + return; + } + if (jsonEnd == std::string::npos) + { + // 兜底补全闭合符 + json_str = rawData.substr(jsonStart) + "}"; + } + else + { + json_str = rawData.substr(jsonStart, jsonEnd - jsonStart + 1); + } + + // JSON解析 + json root; + std::string err; + if (!parseJson(json_str, root, err)) + { + info.ret_code = -3; + info.err_msg = err; + return; + } + + // 字段赋值 + info.map_name = map_name; + info.ret_code = root.value("ret_code", 0); + info.create_on = root.value("create_on", ""); + info.err_msg = root.value("err_msg", ""); + info.map_content = (0 == info.ret_code) ? json_str : ""; +} + + + + // =========================================================================== +// 接口:查询地图状态 → 命令码 1300 (0x0514) +// =========================================================================== +void AgvSrc1100::getMapStatus(MapStatus& info) +{ + int sock = sock_status_; + if (sock < 0) + { + info.ret_code = -1; + info.err_msg = "socket not connected"; + return; + } + + // 步骤1:构造16字节请求帧 + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x05, 0x14, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + if (!sendAndRecv(sock, send_buf)) + { + info.ret_code = -2; + info.err_msg = last_error_; + return; + } + + // 步骤2:数据拷贝 + JSON兜底 + 解析 + std::string json_str; + { + std::lock_guard lk(mutex_); + json_str = current_json_; + } + + if (!json_str.empty()) + { + if (json_str.front() != '{') json_str = "{" + json_str; + if (json_str.back() != '}') json_str += "}"; + } + + if (json_str.empty()) + { + info.ret_code = -4; + info.err_msg = "empty json data"; + return; + } + + json root; + std::string err; + if (!parseJson(json_str, root, err)) + { + info.ret_code = -3; + info.err_msg = err; + return; + } + + // 步骤3:字段赋值 + info.current_map = root.value("current_map", ""); + info.current_map_md5 = root.value("current_map_md5", ""); + info.ret_code = root.value("ret_code", 0); + info.create_on = root.value("create_on", ""); + info.err_msg = root.value("err_msg", ""); + + // 解析地图名数组 + if (root.contains("maps") && root["maps"].is_array()) + { + for (auto& item : root["maps"]) + { + info.maps.push_back(item.get()); + } + } + + // 解析地图文件详情数组 + if (root.contains("map_files_info") && root["map_files_info"].is_array()) + { + for (auto& item : root["map_files_info"]) + { + MapFileInfo fileInfo; + fileInfo.name = item.value("name", ""); + fileInfo.modified = item.value("modified", ""); + fileInfo.size = item.value("size", 0LL); + info.map_files_info.push_back(fileInfo); + } + } +} + + +// =========================================================================== +// 接口:上传地图 → 命令码 4010 (0x0FAA) +// =========================================================================== +void AgvSrc1100::uploadMap(UploadMapResult& info, const std::string& map_json) +{ + int sock = sock_config_; + if (sock < 0) + { + info.ret_code = -1; + info.err_msg = "socket not connected"; + return; + } + + // 清空socket残留数据 + flushSocket(sock); + // 延长IO超时 1s + setSocketTimeout(sock, 1000); + + uint16_t payload_len = static_cast(map_json.size()); + // 帧头:第7、8字节 = 负载长度(大端),修正单字节溢出问题 + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x00, + 0x00, 0x00, + static_cast((payload_len >> 8) & 0xFF), // 高字节 + static_cast(payload_len & 0xFF), // 低字节 + 0x0F, 0xAA, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + // 发送16字节帧头,循环重试 + int totalSent = 0; + int retries = 3; + while (totalSent < 16 && retries-- > 0) + { + int ret = send(sock, send_buf + totalSent, 16 - totalSent, 0); + if (ret <= 0) + { + usleep(10000); + continue; + } + totalSent += ret; + } + if (totalSent != 16) + { + setSocketTimeout(sock, 0); + info.ret_code = -2; + info.err_msg = "send frame header failed"; + return; + } + + // 分片发送大JSON数据,4096字节一片 + const int SLICE_SIZE = 4096; + size_t offset = 0; + size_t totalSize = map_json.size(); + const char* data = map_json.c_str(); + + while (offset < totalSize) + { + int sendLen = std::min((size_t)SLICE_SIZE, totalSize - offset); + int ret = send(sock, data + offset, sendLen, 0); + if (ret <= 0) + { + setSocketTimeout(sock, 0); + info.ret_code = -2; + info.err_msg = "send map payload failed"; + return; + } + offset += ret; + usleep(500); + } + + // 接收响应 + const int RECV_BUF_LEN = 65536; + std::vector recvBuf(RECV_BUF_LEN); + std::string rawData; + int recvRet = 0; + while ((recvRet = recv(sock, recvBuf.data(), RECV_BUF_LEN, 0)) > 0) + { + rawData.append(reinterpret_cast(recvBuf.data()), recvRet); + } + setSocketTimeout(sock, 0); + + if (rawData.empty()) + { + info.ret_code = -2; + info.err_msg = "no response data"; + return; + } + + // 提取JSON + size_t jsonStart = rawData.find('{'); + size_t jsonEnd = rawData.rfind('}'); + std::string json_str; + if (jsonStart == std::string::npos) + { + info.ret_code = -4; + info.err_msg = "json not found"; + return; + } + if (jsonEnd == std::string::npos) + { + json_str = rawData.substr(jsonStart) + "}"; + } + else + { + json_str = rawData.substr(jsonStart, jsonEnd - jsonStart + 1); + } + + // 解析JSON + json root; + std::string err; + if (!parseJson(json_str, root, err)) + { + info.ret_code = -3; + info.err_msg = "json parse error: " + err; + return; + } + + info.ret_code = root.value("ret_code", 0); + info.create_on = root.value("create_on", ""); + info.err_msg = root.value("err_msg", ""); +} + + // =========================================================================== +// 接口:抢占机器人控制权 命令码 4005 (0x0FA5) +// =========================================================================== +/** + * @brief 下发抢占控制权指令到机器人配置端口 + * @param info 输出抢占结果结构体 + * @param nick_name 抢占方标识名称 + */ +void AgvSrc1100::lockRobotControl(LockResult& info, const std::string& nick_name) +{ + // 使用配置端口发送控制指令 + int sock = sock_config_; + if (sock < 0) + { + info.ret_code = -1; + info.err_msg = "socket not connected"; + return; + } + + // 组装请求JSON:{"nick_name":"xxx"} + std::string req_json = R"({"nick_name":")" + nick_name + R"("})"; + // 负载长度双字节大端存储,兼容长字符串 + uint16_t payload_len = static_cast(req_json.size()); + + // 16字节协议帧头,严格对齐设备报文格式 + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, + static_cast((payload_len >> 8) & 0xFF), // 长度高字节 + static_cast(payload_len & 0xFF), // 长度低字节 + 0x0F, 0xA5, // 指令编号 0x0FA5=4005 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + // 清空socket残留脏数据,防止粘包干扰 + flushSocket(sock); + // 设置IO超时1000ms,适配网络延迟 + setSocketTimeout(sock, 1000); + + // 循环发送16字节帧头,最多重试3次 + int total_sent = 0; + int retry = 3; + while (total_sent < 16 && retry > 0) + { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, 0); + if (ret <= 0) + { + retry--; + usleep(10000); + continue; + } + total_sent += ret; + } + // 帧头发送失败直接返回错误 + if (total_sent != 16) + { + setSocketTimeout(sock, 0); + info.ret_code = -2; + info.err_msg = "send frame header failed"; + return; + } + + // 分片发送JSON负载,防止缓冲区溢出 + const int slice_size = 4096; + size_t offset = 0; + size_t total_size = req_json.size(); + const char* data = req_json.c_str(); + while (offset < total_size) + { + int send_len = std::min((size_t)slice_size, total_size - offset); + int ret = send(sock, data + offset, send_len, 0); + if (ret <= 0) + { + setSocketTimeout(sock, 0); + info.ret_code = -2; + info.err_msg = "send payload failed"; + return; + } + offset += ret; + usleep(500); + } + + // 循环读取完整响应数据 + const int recv_buf_len = 65536; + std::vector recv_buf(recv_buf_len); + std::string raw_data; + int recv_ret = 0; + while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) + { + raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); + } + setSocketTimeout(sock, 0); + + // 无响应数据报错 + if (raw_data.empty()) + { + info.ret_code = -2; + info.err_msg = "no response data"; + return; + } + + // 截取完整JSON区间 + size_t json_start = raw_data.find('{'); + size_t json_end = raw_data.rfind('}'); + std::string json_str; + if (json_start == std::string::npos) + { + info.ret_code = -4; + info.err_msg = "json not found in response"; + return; + } + if (json_end == std::string::npos) + { + json_str = raw_data.substr(json_start) + "}"; + } + else + { + json_str = raw_data.substr(json_start, json_end - json_start + 1); + } + + // JSON解析 + json root; + std::string err; + if (!parseJson(json_str, root, err)) + { + info.ret_code = -3; + info.err_msg = "json parse error: " + err; + return; + } + + // 赋值返回字段 + info.ret_code = root.value("ret_code", 0); + info.create_on = root.value("create_on", ""); + info.err_msg = root.value("err_msg", ""); +} + + +// =========================================================================== +// 接口:查询当前控制权所有者 命令码 1060 (0x0424) +// =========================================================================== +/** + * @brief 查询机器人当前抢占控制权信息 + * @param info 输出控制权详情结构体 + */ +void AgvSrc1100::getCurrentLockStatus(CurrentLockStatus& info) +{ + int sock = sock_status_; + if (sock < 0) + { + info.ret_code = -1; + info.err_msg = "socket not connected"; + return; + } + + // 16字节帧头,无JSON负载,负载长度0 + uint16_t payload_len = 0; + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, + static_cast((payload_len >> 8) & 0xFF), + static_cast(payload_len & 0xFF), + 0x04, 0x24, // 命令码 0x0424 = 1060 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + setSocketTimeout(sock, 1000); + + // 循环发送帧头 + int total_sent = 0; + int retry = 3; + while (total_sent < 16 && retry > 0) + { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, 0); + if (ret <= 0) + { + retry--; + usleep(10000); + continue; + } + total_sent += ret; + } + if (total_sent != 16) + { + setSocketTimeout(sock, 0); + info.ret_code = -2; + info.err_msg = "send frame header failed"; + return; + } + + // 接收完整响应 + const int RECV_BUF_LEN = 65536; + std::vector recv_buf(RECV_BUF_LEN); + std::string raw_data; + int recv_ret = 0; + while ((recv_ret = recv(sock, recv_buf.data(), RECV_BUF_LEN, 0)) > 0) + { + raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); + } + setSocketTimeout(sock, 0); + + if (raw_data.empty()) + { + info.ret_code = -2; + info.err_msg = "no response data"; + return; + } + + // 截取JSON + size_t json_start = raw_data.find('{'); + size_t json_end = raw_data.rfind('}'); + std::string json_str; + if (json_start == std::string::npos) + { + info.ret_code = -4; + info.err_msg = "json not found in response"; + return; + } + if (json_end == std::string::npos) + { + json_str = raw_data.substr(json_start) + "}"; + } + else + { + json_str = raw_data.substr(json_start, json_end - json_start + 1); + } + + // JSON解析 + json root; + std::string err; + if (!parseJson(json_str, root, err)) + { + info.ret_code = -3; + info.err_msg = "json parse error: " + err; + return; + } + + // 字段赋值 + info.locked = root.value("locked", false); + info.ip = root.value("ip", ""); + info.port = root.value("port", 0); + info.type = static_cast(root.value("type", 0)); + info.nick_name = root.value("nick_name", ""); + info.time_t = root.value("time_t", 0LL); + info.desc = root.value("desc", ""); + info.ret_code = root.value("ret_code", 0); + info.create_on = root.value("create_on", ""); + info.err_msg = root.value("err_msg", ""); +} + + + // =========================================================================== +// 接口:开环速度运动控制 命令码 2010 (0x07DA) +// 通信端口:sock_control_ 19205控制端口 +// 下发指令强制取消正在执行的导航任务 +// 多舵轮设备仅解析vx/vy/w;steer、real_steer仅单舵轮设备生效 +// =========================================================================== +void AgvSrc1100::robotMotionControl(MotionCtrlRes& res, const MotionCtrlReq& req) +{ + int sock = sock_control_; + if (sock < 0) + { + res.ret_code = -1; + res.err_msg = "socket not connected"; + return; + } + + json send_json; + if (req.vx != 0.0) send_json["vx"] = req.vx; + if (req.vy != 0.0) send_json["vy"] = req.vy; + if (req.w != 0.0) send_json["w"] = req.w; + if (req.steer != 0.0) send_json["steer"] = req.steer; + if (req.real_steer != 0.0) send_json["real_steer"] = req.real_steer; + if (req.duration >= 0) send_json["duration"] = req.duration; + + std::string payload = send_json.dump(); + uint16_t payload_len = static_cast(payload.size()); + + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, + static_cast((payload_len >> 8) & 0xFF), + static_cast(payload_len & 0xFF), + 0x07, 0xDA, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + setSocketTimeout(sock, 1000); + + int total_sent = 0; + int retry_cnt = 3; + while (total_sent < 16 && retry_cnt > 0) + { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); + if (ret <= 0) + { + retry_cnt--; + usleep(10000); + continue; + } + total_sent += ret; + } + if (total_sent != 16) + { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send frame header failed"; + return; + } + + const int slice_size = 4096; + size_t offset = 0; + size_t total_size = payload.size(); + const char* data_ptr = payload.data(); + while (offset < total_size) + { + int send_len = std::min((size_t)slice_size, total_size - offset); + int ret = send(sock, data_ptr + offset, send_len, MSG_NOSIGNAL); + if (ret <= 0) + { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send payload failed"; + return; + } + offset += ret; + usleep(500); + } + + const int recv_buf_len = 65536; + std::vector recv_buf(recv_buf_len); + std::string raw_data; + int recv_ret = 0; + while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) + { + raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); + } + setSocketTimeout(sock, 0); + + if (raw_data.empty()) + { + res.ret_code = -2; + res.err_msg = "no response data"; + return; + } + + size_t json_start = raw_data.find('{'); + size_t json_end = raw_data.rfind('}'); + std::string json_str; + if (json_start == std::string::npos) + { + res.ret_code = -4; + res.err_msg = "json not found in response"; + return; + } + if (json_end == std::string::npos) + { + json_str = raw_data.substr(json_start) + "}"; + } + else + { + json_str = raw_data.substr(json_start, json_end - json_start + 1); + } + + json root; + std::string parse_err; + if (!parseJson(json_str, root, parse_err)) + { + res.ret_code = -3; + res.err_msg = "json parse error: " + parse_err; + return; + } + + res.ret_code = root.value("ret_code", 0); + res.create_on = root.value("create_on", ""); + res.err_msg = root.value("err_msg", ""); +} + + + // =========================================================================== +// 接口:切换载入地图 命令码 2022 (0x07E6) +// 通信端口:sock_control_ 19205控制端口 +// 功能:下发地图名切换设备当前地图,合法名称更新current_map +// =========================================================================== +void AgvSrc1100::robotLoadMap(LoadMapRes& res, const LoadMapReq& req) +{ + + int sock = sock_control_; + if (sock < 0) + { + res.ret_code = -1; + res.err_msg = "socket not connected"; + return; + } + + // 组装请求JSON {"map_name":"xxx"} + json send_json; + send_json["map_name"] = req.map_name; + std::string payload = send_json.dump(); + uint16_t payload_len = static_cast(payload.size()); + + // 16字节协议帧头 0x5A 01 00 01 + 长度大端 + 0x07E6 + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, + static_cast((payload_len >> 8) & 0xFF), + static_cast(payload_len & 0xFF), + 0x07, 0xE6, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + setSocketTimeout(sock, 1000); + + // 重试发送帧头3次 + int total_sent = 0; + int retry_cnt = 3; + while (total_sent < 16 && retry_cnt > 0) + { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); + if (ret <= 0) + { + retry_cnt--; + usleep(10000); + continue; + } + total_sent += ret; + } + if (total_sent != 16) + { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send frame header failed"; + return; + } + + // 分片发送JSON负载 + const int slice_size = 4096; + size_t offset = 0; + size_t total_size = payload.size(); + const char* data_ptr = payload.data(); + while (offset < total_size) + { + int send_len = std::min((size_t)slice_size, total_size - offset); + int ret = send(sock, data_ptr + offset, send_len, MSG_NOSIGNAL); + if (ret <= 0) + { + setSocketTimeout(sock, 3000); + res.ret_code = -2; + res.err_msg = "send payload failed"; + return; + } + offset += ret; + usleep(500); + } + + // 接收响应报文 + const int recv_buf_len = 65536; + std::vector recv_buf(recv_buf_len); + std::string raw_data; + int recv_ret = 0; + while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) + { + raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); + } + setSocketTimeout(sock, 0); + + if (raw_data.empty()) + { + res.ret_code = -2; + res.err_msg = "no response data"; + return; + } + + // 截取JSON,兜底补全闭合符 + size_t json_start = raw_data.find('{'); + size_t json_end = raw_data.rfind('}'); + std::string json_str; + if (json_start == std::string::npos) + { + res.ret_code = -4; + res.err_msg = "json not found in response"; + return; + } + if (json_end == std::string::npos) + { + json_str = raw_data.substr(json_start) + "}"; + } + else + { + json_str = raw_data.substr(json_start, json_end - json_start + 1); + } + + // 解析JSON + json root; + std::string parse_err; + if (!parseJson(json_str, root, parse_err)) + { + res.ret_code = -3; + res.err_msg = "json parse error: " + parse_err; + return; + } + + res.ret_code = root.value("ret_code", 0); + res.create_on = root.value("create_on", ""); + res.err_msg = root.value("err_msg", ""); + LOG(INFO) << "recv raw_data len=" << raw_data.size() << " content=" << raw_data; + +} + + // =========================================================================== +// 接口:查询地图载入状态 命令码 1022 (0x03FE) +// 通信端口:sock_status_ 19204状态端口 +// loadmap_status 0=失败 1=成功 2=载入中;载入中禁止重定位 +// =========================================================================== +void AgvSrc1100::queryLoadMapStatus(QueryLoadMapStatusRes& res) +{ + int sock = sock_status_; + if (sock < 0) + { + res.ret_code = -1; + res.err_msg = "socket not connected"; + return; + } + + // 无负载,负载长度0 + uint16_t payload_len = 0; + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, + static_cast((payload_len >> 8) & 0xFF), + static_cast(payload_len & 0xFF), + 0x03, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + setSocketTimeout(sock, 1000); + + int total_sent = 0; + int retry_cnt = 3; + while (total_sent < 16 && retry_cnt > 0) + { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); + if (ret <= 0) + { + retry_cnt--; + usleep(10000); + continue; + } + total_sent += ret; + } + if (total_sent != 16) + { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send frame header failed"; + return; + } + + const int recv_buf_len = 65536; + std::vector recv_buf(recv_buf_len); + std::string raw_data; + int recv_ret = 0; + while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) + { + raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); + } + setSocketTimeout(sock, 0); + + if (raw_data.empty()) + { + res.ret_code = -2; + res.err_msg = "no response data"; + return; + } + + size_t json_start = raw_data.find('{'); + size_t json_end = raw_data.rfind('}'); + std::string json_str; + if (json_start == std::string::npos) + { + res.ret_code = -4; + res.err_msg = "json not found in response"; + return; + } + if (json_end == std::string::npos) + { + json_str = raw_data.substr(json_start) + "}"; + } + else + { + json_str = raw_data.substr(json_start, json_end - json_start + 1); + } + + json root; + std::string parse_err; + if (!parseJson(json_str, root, parse_err)) + { + res.ret_code = -3; + res.err_msg = "json parse error: " + parse_err; + return; + } + + res.loadmap_status = root.value("loadmap_status", 0); + res.ret_code = root.value("ret_code", 0); + res.create_on = root.value("create_on", ""); + res.err_msg = root.value("err_msg", ""); +} + + + // =========================================================================== +// 接口:查询当前地图站点列表 命令码 1301 (0x0515) +// 通信端口:sock_status_ 19204状态端口 +// 功能:获取当前加载地图所有LocationMark/ChargePoint/ActionPoint站点 +// =========================================================================== +void AgvSrc1100::queryStationList(QueryStationRes& res) +{ + int sock = sock_status_; + if (sock < 0) + { + res.ret_code = -1; + res.err_msg = "socket not connected"; + return; + } + + uint16_t payload_len = 0; + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, + static_cast((payload_len >> 8) & 0xFF), + static_cast(payload_len & 0xFF), + 0x05, 0x15, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + setSocketTimeout(sock, 1000); + + int total_sent = 0; + int retry_cnt = 3; + while (total_sent < 16 && retry_cnt > 0) + { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); + if (ret <= 0) + { + retry_cnt--; + usleep(10000); + continue; + } + total_sent += ret; + } + if (total_sent != 16) + { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send frame header failed"; + return; + } + + const int recv_buf_len = 65536; + std::vector recv_buf(recv_buf_len); + std::string raw_data; + int recv_ret = 0; + while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) + { + raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); + } + setSocketTimeout(sock, 0); + + if (raw_data.empty()) + { + res.ret_code = -2; + res.err_msg = "no response data"; + return; + } + + size_t json_start = raw_data.find('{'); + size_t json_end = raw_data.rfind('}'); + std::string json_str; + if (json_start == std::string::npos) + { + res.ret_code = -4; + res.err_msg = "json not found in response"; + return; + } + if (json_end == std::string::npos) + { + json_str = raw_data.substr(json_start) + "}"; + } + else + { + json_str = raw_data.substr(json_start, json_end - json_start + 1); + } + + json root; + std::string parse_err; + if (!parseJson(json_str, root, parse_err)) + { + res.ret_code = -3; + res.err_msg = "json parse error: " + parse_err; + return; + } + + res.ret_code = root.value("ret_code", 0); + res.create_on = root.value("create_on", ""); + res.err_msg = root.value("err_msg", ""); + + // 解析站点数组 + // 解析站点数组 + if (root.contains("stations") && root["stations"].is_array()) + { + for (auto& item : root["stations"]) + { + StationItem st; + st.id = item.value("id", ""); + st.type = item.value("type", ""); + st.x = item.value("x", 0.0); + st.y = item.value("y", 0.0); + st.r = item.value("r", 0.0); + st.desc = item.value("desc", ""); + st.executor = item.value("executor", ""); + st.prepoint = item.value("prepoint", ""); + st.recfile = item.value("recfile", ""); + + // 兼容bool/字符串"true"/"false" + if (item.contains("spin")) + { + if (item["spin"].is_boolean()) + st.spin = item["spin"].get(); + else if (item["spin"].is_string()) + st.spin = (item["spin"].get() == "true"); + } + if (item.contains("use_down_pgv")) + { + if (item["use_down_pgv"].is_boolean()) + st.use_down_pgv = item["use_down_pgv"].get(); + else if (item["use_down_pgv"].is_string()) + st.use_down_pgv = (item["use_down_pgv"].get() == "true"); + } + + res.stations.push_back(st); + } + } +} + + +// =========================================================================== +// 接口:指定路径导航 命令码 3066 (0x0BFA) +// 通信端口:sock_control_ 19205控制端口 +// 功能:下发多段连续站点导航任务序列,任务串行排队执行 +// 必填字段:task_id / source_id / id +// =========================================================================== +void AgvSrc1100::robotGoTargetList(GoTargetListRes& res, const GoTargetListReq& req) +{ + int sock = sock_nav_; // ✅ 修正:使用 sock_nav_ 而非 port_nav_ + LOG(INFO) << "robotGoTargetList sock fd=" << sock; + if (sock < 0) + { + res.ret_code = -1; + res.err_msg = "nav socket invalid"; // 更新错误信息 + return; + } + + // 组装请求JSON + json send_json; + json arr = json::array(); + for (const auto& task : req.move_task_list) + { + json obj; + obj["task_id"] = task.task_id; + obj["source_id"] = task.source_id; + obj["id"] = task.id; + if (!task.operation.empty()) + { + obj["operation"] = task.operation; + obj["jack_height"] = task.jack_height; + } + arr.push_back(obj); + } + send_json["move_task_list"] = arr; + std::string payload = send_json.dump(); + uint16_t payload_len = static_cast(payload.size()); + LOG(INFO) << "nav payload length:" << payload_len; + + // 静态帧头缓冲区 + static uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, + 0,0, + 0x0B, 0xFA, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + send_buf[6] = static_cast((payload_len >> 8) & 0xFF); + send_buf[7] = static_cast(payload_len & 0xFF); + + flushSocket(sock); + setSocketTimeout(sock, 3000); + + // 发送16字节帧头 + int total_sent = 0; + int retry_cnt = 5; + while (total_sent < 16 && retry_cnt > 0) + { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); + if (ret <= 0) + { + LOG(ERROR) << "send header failed, ret=" << ret + << ", errno=" << errno + << " (" << strerror(errno) << ")"; + retry_cnt--; + usleep(20000); + continue; // 不再检查 sock_control_,因为 sock 已在开头验证 + } + total_sent += ret; + } + if (total_sent != 16) + { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send frame header failed"; + return; + } + + // 分片发送,缩小分片大小至1024 + const int slice_size = 1024; + size_t offset = 0; + size_t total_size = payload.size(); + const char* data_ptr = payload.data(); + while (offset < total_size) + { + int send_len = std::min((size_t)slice_size, total_size - offset); + int ret = send(sock, data_ptr + offset, send_len, MSG_NOSIGNAL); + if (ret <= 0) + { + LOG(ERROR) << "send payload failed, errno=" << errno << " (" << strerror(errno) << ")"; + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send payload failed"; + return; + } + offset += ret; + } + + // 接收并解析响应 + const int recv_buf_len = 65536; + std::vector recv_buf(recv_buf_len); + std::string raw_data; + int recv_ret = 0; + while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, MSG_NOSIGNAL)) > 0) + { + raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); + } + setSocketTimeout(sock, 0); + + if (raw_data.empty()) + { + res.ret_code = -2; + res.err_msg = "no response data"; + return; + } + + size_t json_start = raw_data.find('{'); + if (json_start == std::string::npos) + { + res.ret_code = -4; + res.err_msg = "json not found in response"; + return; + } + std::string json_str = raw_data.substr(json_start); + if (json_str.back() != '}') + { + json_str += "}"; + } + + json root; + std::string parse_err; + try + { + root = json::parse(json_str); + } + catch (json::parse_error& e) + { + res.ret_code = -3; + res.err_msg = "json parse error: " + std::string(e.what()); + return; + } + + res.ret_code = root.contains("ret_code") ? root["ret_code"].get() : 0; + res.create_on = root.contains("create_on") ? root["create_on"].get() : ""; + res.err_msg = root.contains("err_msg") ? root["err_msg"].get() : ""; +} + + + //=========================================================================== +// 1020 robot_status_task_req 0x03FC 查询当前导航状态 sock_status_ 19204 +// 应答编号 11020 0x2B0C +//=========================================================================== +void AgvSrc1100::robotStatusTask(RobotStatusTaskRes& res, const RobotStatusTaskReq& req) +{ + int sock = sock_status_; + LOG(INFO) << "robotStatusTask sock fd=" << sock; + if (sock < 0) + { + res.ret_code = -1; + res.err_msg = "status port socket invalid"; + return; + } + + // 组装请求JSON + json send_json; + if (req.simple) + { + send_json["simple"] = true; + } + std::string payload = send_json.dump(); + uint16_t payload_len = static_cast(payload.size()); + + // 帧头 0x03FC = 1020 + static uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, + 0,0, + 0x03, 0xFC, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + send_buf[6] = static_cast((payload_len >> 8) & 0xFF); + send_buf[7] = static_cast(payload_len & 0xFF); + + flushSocket(sock); + setSocketTimeout(sock, 3000); + + // 发送帧头 5次重试 + int total_sent = 0; + int retry_cnt = 5; + while (total_sent < 16 && retry_cnt > 0) + { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); + if (ret <= 0) + { + retry_cnt--; + usleep(20000); + if (sock_status_ < 0) + { + setSocketTimeout(sock, 0); + res.ret_code = -1; + res.err_msg = "socket broken send header"; + return; + } + continue; + } + total_sent += ret; + } + if (total_sent != 16) + { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send frame header failed"; + return; + } + + // 分片发送payload + const int slice_size = 1024; + size_t offset = 0; + size_t total_size = payload.size(); + const char* data_ptr = payload.data(); + while (offset < total_size) + { + int send_len = std::min((size_t)slice_size, total_size - offset); + int ret = send(sock, data_ptr + offset, send_len, MSG_NOSIGNAL); + if (ret <= 0) + { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send payload failed"; + return; + } + offset += ret; + } + + // 接收应答 + const int recv_buf_len = 65536; + std::vector recv_buf(recv_buf_len); + std::string raw_data; + int recv_ret = 0; + while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, MSG_NOSIGNAL)) > 0) + { + raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); + } + setSocketTimeout(sock, 0); + + if (raw_data.empty()) + { + res.ret_code = -2; + res.err_msg = "no response data"; + return; + } + + // 截取纯JSON + size_t json_start = raw_data.find('{'); + if (json_start == std::string::npos) + { + res.ret_code = -4; + res.err_msg = "json not found in response"; + return; + } + std::string json_str = raw_data.substr(json_start); + if (json_str.back() != '}') + { + json_str += "}"; + } + + // JSON解析捕获异常 + json root; + try + { + root = json::parse(json_str); + } + catch (json::parse_error& e) + { + res.ret_code = -3; + res.err_msg = "json parse error: " + std::string(e.what()); + return; + } + + // 顶层公共字段 + res.ret_code = root.contains("ret_code") ? root["ret_code"].get() : 0; + res.create_on = root.contains("create_on") ? root["create_on"].get() : ""; + res.err_msg = root.contains("err_msg") ? root["err_msg"].get() : ""; + + // 导航基础状态 + res.task_status = root.contains("task_status") ? root["task_status"].get() : 0; + res.task_type = root.contains("task_type") ? root["task_type"].get() : 0; + res.target_id = root.contains("target_id") ? root["target_id"].get() : ""; + res.move_status_info = root.contains("move_status_info") ? root["move_status_info"].get() : ""; + + // target_point x,y,r + if (root.contains("target_point") && root["target_point"].is_array()) + { + for (auto& val : root["target_point"]) + { + res.target_point.push_back(val.get()); + } + } + + // 已走过站点 + if (root.contains("finished_path") && root["finished_path"].is_array()) + { + for (auto& s : root["finished_path"]) + { + res.finished_path.push_back(s.get()); + } + } + // 未走站点 + if (root.contains("unfinished_path") && root["unfinished_path"].is_array()) + { + for (auto& s : root["unfinished_path"]) + { + res.unfinished_path.push_back(s.get()); + } + } + + // 料箱数组 + if (root.contains("containers") && root["containers"].is_array()) + { + for (auto& item : root["containers"]) + { + NavContainerItem c; + c.container_name = item.contains("container_name") ? item["container_name"].get() : ""; + c.desc = item.contains("desc") ? item["desc"].get() : ""; + c.goods_id = item.contains("goods_id") ? item["goods_id"].get() : ""; + if (item.contains("has_goods")) + { + if (item["has_goods"].is_boolean()) + c.has_goods = item["has_goods"].get(); + else if (item["has_goods"].is_string()) + c.has_goods = (item["has_goods"].get() == "true"); + } + res.containers.push_back(c); + } + } +} + + + +} // namespace cmvr::device + + + diff --git a/cmvr-es/service/CMakeLists.txt b/cmvr-es/service/CMakeLists.txt index 4ef50063..98ea4199 100644 --- a/cmvr-es/service/CMakeLists.txt +++ b/cmvr-es/service/CMakeLists.txt @@ -9,6 +9,7 @@ add_library(service grpc/src/grpc_humanoid_robot_service.cpp grpc/src/grpc_hlc_service.cpp grpc/src/server_runner.cpp + grpc/src/grpc_agv_service.cpp ) target_include_directories(service PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/cmvr-es/service/grpc/include/grpc_agv_service.h b/cmvr-es/service/grpc/include/grpc_agv_service.h new file mode 100644 index 00000000..d620c474 --- /dev/null +++ b/cmvr-es/service/grpc/include/grpc_agv_service.h @@ -0,0 +1,131 @@ +// +// Created by xtkuang on 2025/6/1. +// + +#ifndef GRPC_AGV_SERVICE_H +#define GRPC_AGV_SERVICE_H + +#include "cmvr/api/agv_service.grpc.pb.h" +#include "utils/base/include/grpc_utils.h" +#include "device_manager/include/device_manager.h" + +namespace cmvr::service { + + class gRPCAGVServiceImpl final : public api::AgvService::Service { + public: + gRPCAGVServiceImpl(); + ~gRPCAGVServiceImpl() override = default; + + // AGV服务接口 + grpc::Status GetStatusInfo(grpc::ServerContext* context, + const api::GetAgvStatusInfoCommand_Request* request, + api::GetAgvStatusInfoCommand_Feedback* response) override; + + grpc::Status GetBatteryStatus(grpc::ServerContext* context, + const api::RobotStatusBatteryCommand_Request* request, + api::RobotStatusBatteryCommand_Feedback* response) override; + + grpc::Status GetRobotLocation(grpc::ServerContext* context, + const api::RobotStatusLocCommand_Request* request, + api::RobotStatusLocCommand_Feedback* response) override; + + grpc::Status RobotConfigDownloadMap(grpc::ServerContext* context, + const api::RobotConfigDownloadMapCommand_Request* request, + api::RobotConfigDownloadMapCommand_Feedback* response) override; + + grpc::Status GetMapStatus(grpc::ServerContext* context, + const api::RobotStatusMapCommand_Request* request, + api::RobotStatusMapCommand_Feedback* response) override; + + grpc::Status RobotConfigUploadMap(grpc::ServerContext* context, + const api::RobotConfigUploadMapCommand_Request* request, + api::RobotConfigUploadMapCommand_Feedback* response) override; + /** + * @brief gRPC接口:抢占机器人控制权 + */ + grpc::Status RobotConfigLock(grpc::ServerContext* context, + const api::RobotConfigLockCommand_Request* request, + api::RobotConfigLockCommand_Feedback* response) override; + + + + /** + * @brief gRPC接口 查询当前控制权持有者 + */ + grpc::Status GetCurrentLockStatus(grpc::ServerContext* context, + const api::RobotStatusCurrentLockCommand_Request* request, + api::RobotStatusCurrentLockCommand_Feedback* response) override; + + /** + * @brief AGV开环速度运动控制 + * @param context grpc上下文 + * @param request 请求体,header携带device_id区分多AGV + * @param response 运动指令执行结果 + */ + grpc::Status RobotMotionControl(grpc::ServerContext* context, + const api::RobotMotionControlCommand_Request* request, + api::RobotMotionControlCommand_Feedback* response) override; + + + /** + * @brief 切换载入地图 + * @param context grpc上下文 + * @param request 请求体,携带目标地图名称 + * @param response 切换地图执行结果 + */ + grpc::Status RobotLoadMap( + grpc::ServerContext* context, + const api::RobotLoadMapCommand_Request* request, + api::RobotLoadMapCommand_Feedback* response) override; + + /** + * @brief 查询地图载入状态 + * @param context grpc上下文 + * @param request 请求头 + * @param response 地图加载状态结果 + */ + grpc::Status QueryLoadMapStatus( + grpc::ServerContext* context, + const api::RobotQueryLoadMapStatusCommand_Request* request, + api::RobotQueryLoadMapStatusCommand_Feedback* response) override; + + /** + * @brief 查询当前地图所有站点信息 + * @param context grpc上下文 + * @param request 请求头 + * @param response 站点列表结果 + */ + grpc::Status QueryStationList( + grpc::ServerContext* context, + const api::QueryStationListCommand_Request* request, + api::QueryStationListCommand_Feedback* response) override; + + + /** + * @brief 指定路径连续站点导航 + * @param context grpc上下文 + * @param request 导航任务列表请求 + * @param response 下发任务返回结果 + */ + grpc::Status RobotGoTargetList( + grpc::ServerContext* context, + const api::RobotGoTargetListCommand_Request* request, + api::RobotGoTargetListCommand_Feedback* response) override; + + + grpc::Status RobotStatusTask( + grpc::ServerContext* context, + const api::RobotStatusTaskCommand_Request* request, + api::RobotStatusTaskCommand_Feedback* response) override; + + + + + + + private: + device::DeviceManager& dmgr_; + }; +} + +#endif //GRPC_AGV_SERVICE_H \ No newline at end of file diff --git a/cmvr-es/service/grpc/src/grpc_agv_service.cpp b/cmvr-es/service/grpc/src/grpc_agv_service.cpp new file mode 100644 index 00000000..8b7fd741 --- /dev/null +++ b/cmvr-es/service/grpc/src/grpc_agv_service.cpp @@ -0,0 +1,708 @@ +// +// Created by xtkuang on 2025/6/1. +// + +#include "./service/grpc/include/grpc_agv_service.h" + +#include + +#include "devices/agv/agv_src1100/include/agv_src1100.h" + +#include "devices/agv/abstract_agv.h" + +namespace cmvr::service { + + gRPCAGVServiceImpl::gRPCAGVServiceImpl() : dmgr_(device::DeviceManager::getInstance()) { + } + + grpc::Status gRPCAGVServiceImpl::GetStatusInfo( + grpc::ServerContext* context, + const api::GetAgvStatusInfoCommand_Request* request, + api::GetAgvStatusInfoCommand_Feedback* response) + { + try { + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (GetStatusInfo): id=" << dev_id; + const auto agv_device = dmgr_.getDevice(dev_id); + + device::AbstractAgv::AgvStatusInfo device_status; + agv_device->getStatusInfo(device_status); + + auto* proto_status = response->mutable_status(); + proto_status->set_id(device_status.id); + proto_status->set_vehicle_id(device_status.vehicle_id); + proto_status->set_version(device_status.version); + proto_status->set_model(device_status.model); + proto_status->set_dsp_version(device_status.dsp_version); + proto_status->set_current_ip(device_status.current_ip); + proto_status->set_mac(device_status.mac); + proto_status->set_rssi(device_status.rssi); + proto_status->set_ret_code(device_status.ret_code); + proto_status->set_err_msg(device_status.err_msg); + + auto* header = response->mutable_header(); + header->set_success(true); + header->set_error_message(""); + setCurrentTimestamp(header->mutable_timestamp()); + + LOG(INFO) << "GetStatusInfo device_id:" << dev_id << " vehicle_id: " << device_status.vehicle_id; + return grpc::Status::OK; + } + catch (std::exception &e) + { + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message(e.what()); + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } + } + + +grpc::Status gRPCAGVServiceImpl::GetBatteryStatus( +grpc::ServerContext* context, +const api::RobotStatusBatteryCommand_Request* request, +api::RobotStatusBatteryCommand_Feedback* response) +{ + try { + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (GetBatteryStatus): id=" << dev_id; + const auto agv_device = dmgr_.getDevice(dev_id); + + bool simple = false; + if (request->has_data()) + { + simple = request->data().simple(); + } + + device::AbstractAgv::BatteryStatus bat_info; + agv_device->getBatteryStatus(bat_info, simple); + + auto* proto_bat = response->mutable_status(); + proto_bat->set_battery_level(bat_info.battery_level); + proto_bat->set_battery_temp(bat_info.battery_temp); + proto_bat->set_charging(bat_info.charging); + proto_bat->set_voltage(bat_info.voltage); + proto_bat->set_current(bat_info.current); + proto_bat->set_max_charge_voltage(bat_info.max_charge_voltage); + proto_bat->set_max_charge_current(bat_info.max_charge_current); + proto_bat->set_manual_charge(bat_info.manual_charge); + proto_bat->set_auto_charge(bat_info.auto_charge); + proto_bat->set_battery_cycle(bat_info.battery_cycle); + proto_bat->set_battery_user_data(bat_info.battery_user_data); + proto_bat->set_extra(bat_info.extra); + proto_bat->set_ret_code(bat_info.ret_code); + proto_bat->set_create_on(bat_info.create_on); + proto_bat->set_err_msg(bat_info.err_msg); + + auto* header = response->mutable_header(); + header->set_success(true); + header->set_error_message(""); + setCurrentTimestamp(header->mutable_timestamp()); + + LOG(INFO) << "GetBatteryStatus device_id:" << dev_id << " level: " << bat_info.battery_level; + return grpc::Status::OK; + } + catch (std::exception &e) + { + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message(e.what()); + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } +} + + + grpc::Status gRPCAGVServiceImpl::GetRobotLocation( + grpc::ServerContext* context, + const api::RobotStatusLocCommand_Request* request, + api::RobotStatusLocCommand_Feedback* response) + { + try { + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (GetRobotLocation): id=" << dev_id; + const auto agv_device = dmgr_.getDevice(dev_id); + + device::AbstractAgv::RobotLocation loc_info; + agv_device->getRobotLocation(loc_info); + + auto* proto_loc = response->mutable_status(); + proto_loc->set_x(loc_info.x); + proto_loc->set_y(loc_info.y); + proto_loc->set_angle(loc_info.angle); + proto_loc->set_confidence(loc_info.confidence); + proto_loc->set_current_station(loc_info.current_station); + proto_loc->set_last_station(loc_info.last_station); + proto_loc->set_loc_method(loc_info.loc_method); + proto_loc->set_ret_code(loc_info.ret_code); + proto_loc->set_create_on(loc_info.create_on); + proto_loc->set_err_msg(loc_info.err_msg); + + auto* header = response->mutable_header(); + header->set_success(true); + header->set_error_message(""); + setCurrentTimestamp(header->mutable_timestamp()); + + LOG(INFO) << "GetRobotLocation device_id:" << dev_id << " x: " << loc_info.x << ", y: " << loc_info.y; + return grpc::Status::OK; + } + catch (std::exception &e) + { + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message(e.what()); + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } + } + + + grpc::Status gRPCAGVServiceImpl::RobotConfigDownloadMap( + grpc::ServerContext* context, + const api::RobotConfigDownloadMapCommand_Request* request, + api::RobotConfigDownloadMapCommand_Feedback* response) + { + try { + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (RobotConfigDownloadMap): id=" << dev_id; + const auto agv_device = dmgr_.getDevice(dev_id); + + std::string map_name = request->data().map_name(); + device::AbstractAgv::DownloadMapResult down_info; + agv_device->downloadMap(down_info, map_name); + + auto* proto_res = response->mutable_status(); + proto_res->set_ret_code(down_info.ret_code); + proto_res->set_create_on(down_info.create_on); + proto_res->set_err_msg(down_info.err_msg); + proto_res->set_map_content(down_info.map_content); + + auto* header = response->mutable_header(); + header->set_success(true); + header->set_error_message(""); + setCurrentTimestamp(header->mutable_timestamp()); + + LOG(INFO) << "DownloadMap device_id:" << dev_id << " map: " << map_name << ", ret_code: " << down_info.ret_code; + return grpc::Status::OK; + } + catch (std::exception &e) + { + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message(e.what()); + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } + } + + + + grpc::Status gRPCAGVServiceImpl::GetMapStatus( + grpc::ServerContext* context, + const api::RobotStatusMapCommand_Request* request, + api::RobotStatusMapCommand_Feedback* response) + { + try { + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (GetMapStatus): id=" << dev_id; + const auto agv_device = dmgr_.getDevice(dev_id); + + device::AbstractAgv::MapStatus map_info; + agv_device->getMapStatus(map_info); + + auto* proto_map = response->mutable_status(); + proto_map->set_current_map(map_info.current_map); + proto_map->set_current_map_md5(map_info.current_map_md5); + proto_map->set_ret_code(map_info.ret_code); + proto_map->set_create_on(map_info.create_on); + proto_map->set_err_msg(map_info.err_msg); + + for (auto& name : map_info.maps) + { + proto_map->add_maps(name); + } + + for (auto& file : map_info.map_files_info) + { + auto* proto_file = proto_map->add_map_files_info(); + proto_file->set_name(file.name); + proto_file->set_modified(file.modified); + proto_file->set_size(file.size); + } + + auto* header = response->mutable_header(); + header->set_success(true); + header->set_error_message(""); + setCurrentTimestamp(header->mutable_timestamp()); + + LOG(INFO) << "GetMapStatus device_id:" << dev_id << " current map: " << map_info.current_map; + return grpc::Status::OK; + } + catch (std::exception &e) + { + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message(e.what()); + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } + } + + + grpc::Status gRPCAGVServiceImpl::RobotConfigUploadMap( + grpc::ServerContext* context, + const api::RobotConfigUploadMapCommand_Request* request, + api::RobotConfigUploadMapCommand_Feedback* response) + { + try { + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (RobotConfigUploadMap): id=" << dev_id; + const auto agv_device = dmgr_.getDevice(dev_id); + + std::string map_json = request->data().map_content(); + device::AbstractAgv::UploadMapResult upload_info; + agv_device->uploadMap(upload_info, map_json); + + auto* proto_res = response->mutable_status(); + proto_res->set_ret_code(upload_info.ret_code); + proto_res->set_create_on(upload_info.create_on); + proto_res->set_err_msg(upload_info.err_msg); + + auto* header = response->mutable_header(); + header->set_success(true); + header->set_error_message(""); + setCurrentTimestamp(header->mutable_timestamp()); + + LOG(INFO) << "UploadMap device_id:" << dev_id << " ret_code: " << upload_info.ret_code; + return grpc::Status::OK; + } + catch (std::exception &e) + { + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message(e.what()); + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } + } + + + /** + * @brief gRPC 抢占机器人控制权接口 + * @param context grpc上下文 + * @param request 前端请求参数,携带nick_name抢占者名称 + * @param response 抢占结果返回体 + */ + grpc::Status gRPCAGVServiceImpl::RobotConfigLock( + grpc::ServerContext* context, + const api::RobotConfigLockCommand_Request* request, + api::RobotConfigLockCommand_Feedback* response) + { + try { + // 动态从header拿设备ID,和查询接口统一 + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (RobotConfigLock): id=" << dev_id; + + // 根据device_id获取对应AGV实例 + const auto agv_device = dmgr_.getDevice(dev_id); + + // 读取抢占者名称 + std::string nick_name = request->data().nick_name(); + device::AbstractAgv::LockResult lock_res; + // 调用底层抢占接口 + agv_device->lockRobotControl(lock_res, nick_name); + + // 填充protobuf返回数据 + auto* proto_status = response->mutable_status(); + proto_status->set_ret_code(lock_res.ret_code); + proto_status->set_create_on(lock_res.create_on); + proto_status->set_err_msg(lock_res.err_msg); + + // 响应头标记成功 + 时间戳对齐全局规范 + auto* header = response->mutable_header(); + header->set_success(true); + header->set_error_message(""); + setCurrentTimestamp(header->mutable_timestamp()); + + LOG(INFO) << "Lock robot control, device_id:" << dev_id << ", nick:" << nick_name << ", ret_code:" << lock_res.ret_code; + return grpc::Status::OK; + } + catch (std::exception &e) + { + // 统一异常捕获,和相机/查询接口风格完全一致 + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message(e.what()); + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } + } + + +/** + * @brief 查询机器人当前控制权信息gRPC接口 + */ +grpc::Status gRPCAGVServiceImpl::GetCurrentLockStatus( + grpc::ServerContext* context, + const api::RobotStatusCurrentLockCommand_Request* request, + api::RobotStatusCurrentLockCommand_Feedback* response) +{ + try { + // 1. 读取请求device_id,打印日志(和相机代码保持一致) + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (GetCurrentLockStatus): id=" << dev_id; + + // 2. 按device_id获取AGV设备实例,和相机 dmgr_.getDevice(dev_id) 统一写法 + const auto agv_dev = dmgr_.getDevice(dev_id); + + // 3. 调用底层接口获取控制权信息 + device::AbstractAgv::CurrentLockStatus lock_info; + agv_dev->getCurrentLockStatus(lock_info); + + // 4. 填充protobuf返回结构体 + auto* proto_status = response->mutable_status(); + proto_status->set_locked(lock_info.locked); + proto_status->set_ip(lock_info.ip); + proto_status->set_port(lock_info.port); + proto_status->set_type(lock_info.type); + proto_status->set_nick_name(lock_info.nick_name); + proto_status->set_time_t(lock_info.time_t); + proto_status->set_desc(lock_info.desc); + proto_status->set_ret_code(lock_info.ret_code); + proto_status->set_create_on(lock_info.create_on); + proto_status->set_err_msg(lock_info.err_msg); + + // 5. 响应header标记成功 + 填充时间戳(对齐相机接口) + response->mutable_header()->set_success(true); + setCurrentTimestamp(response->mutable_header()->mutable_timestamp()); + + LOG(INFO) << "Query current lock status, device_id:" << dev_id + << ", locked:" << lock_info.locked << ", nick:" << lock_info.nick_name; + return grpc::Status::OK; + } + catch (std::exception &e) + { + // 异常统一捕获,失败header,填错误信息+时间戳,和相机完全对齐 + response->mutable_header()->set_success(false); + response->mutable_header()->set_error_message(e.what()); + setCurrentTimestamp(response->mutable_header()->mutable_timestamp()); + return grpc::Status::OK; + } +} + + + grpc::Status gRPCAGVServiceImpl::RobotMotionControl( + grpc::ServerContext* context, + const api::RobotMotionControlCommand_Request* request, + api::RobotMotionControlCommand_Feedback* response) + { + try { + // 从header读取device_id,区分多台AGV设备 + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (RobotMotionControl): target device_id=" << dev_id; + const auto agv_dev = dmgr_.getDevice(dev_id); + + // grpc入参转换到底层结构体 + device::AbstractAgv::MotionCtrlReq req; + auto& input_data = request->data(); + req.vx = input_data.vx(); + req.vy = input_data.vy(); + req.w = input_data.w(); + req.steer = input_data.steer(); + req.real_steer = input_data.real_steer(); + req.duration = input_data.duration(); + + // 调用底层运动控制接口 + device::AbstractAgv::MotionCtrlRes res; + agv_dev->robotMotionControl(res, req); + + // 填充grpc返回状态 + auto* resp_status = response->mutable_status(); + resp_status->set_ret_code(res.ret_code); + resp_status->set_create_on(res.create_on); + resp_status->set_err_msg(res.err_msg); + + // 统一规范header:成功标记+时间戳 + auto* header = response->mutable_header(); + header->set_success(true); + header->set_error_message(""); + setCurrentTimestamp(header->mutable_timestamp()); + + LOG(INFO) << "RobotMotionControl finish, dev_id:" << dev_id + << " vx=" << req.vx << " vy=" << req.vy << " w=" << req.w + << " ret_code=" << res.ret_code; + return grpc::Status::OK; + } + catch (std::exception& e) + { + // 全局统一异常捕获模板 + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message(e.what()); + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } + } + + grpc::Status gRPCAGVServiceImpl::RobotLoadMap( + grpc::ServerContext* context, + const api::RobotLoadMapCommand_Request* request, + api::RobotLoadMapCommand_Feedback* response) + { + try { + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (RobotLoadMap): target device_id=" << dev_id; + const auto agv_dev = dmgr_.getDevice(dev_id); + + device::AbstractAgv::LoadMapReq req; + auto& input_data = request->data(); + req.map_name = input_data.map_name(); + + device::AbstractAgv::LoadMapRes res; + agv_dev->robotLoadMap(res, req); + + auto* resp_status = response->mutable_status(); + resp_status->set_ret_code(res.ret_code); + resp_status->set_create_on(res.create_on); + resp_status->set_err_msg(res.err_msg); + + auto* header = response->mutable_header(); + header->set_success(true); + header->set_error_message(""); + // 修复:取timestamp子字段 + setCurrentTimestamp(header->mutable_timestamp()); + + LOG(INFO) << "RobotLoadMap finish, dev_id:" << dev_id + << " map_name=" << req.map_name + << " ret_code=" << res.ret_code; + return grpc::Status::OK; + } + catch (std::exception& e) + { + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message(e.what()); + // 修复:取timestamp子字段 + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } + } + + + grpc::Status gRPCAGVServiceImpl::QueryLoadMapStatus( + grpc::ServerContext* context, + const api::RobotQueryLoadMapStatusCommand_Request* request, + api::RobotQueryLoadMapStatusCommand_Feedback* response) + { + try { + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (QueryLoadMapStatus): device_id=" << dev_id; + const auto agv_dev = dmgr_.getDevice(dev_id); + + device::AbstractAgv::QueryLoadMapStatusRes res; + agv_dev->queryLoadMapStatus(res); + + auto* resp_status = response->mutable_status(); + resp_status->set_loadmap_status(res.loadmap_status); + resp_status->set_ret_code(res.ret_code); + resp_status->set_create_on(res.create_on); + resp_status->set_err_msg(res.err_msg); + + auto* header = response->mutable_header(); + header->set_success(true); + header->set_error_message(""); + setCurrentTimestamp(header->mutable_timestamp()); + + LOG(INFO) << "QueryLoadMapStatus finish dev_id:" << dev_id + << " loadmap_status=" << res.loadmap_status + << " ret_code=" << res.ret_code; + return grpc::Status::OK; + } + catch (std::exception& e) + { + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message(e.what()); + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } + } + + grpc::Status gRPCAGVServiceImpl::QueryStationList( + grpc::ServerContext* context, + const api::QueryStationListCommand_Request* request, + api::QueryStationListCommand_Feedback* response) + { + try { + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (QueryStationList): device_id=" << dev_id; + const auto agv_dev = dmgr_.getDevice(dev_id); + + device::AbstractAgv::QueryStationRes res; + agv_dev->queryStationList(res); + + auto* resp_status = response->mutable_status(); + resp_status->set_ret_code(res.ret_code); + resp_status->set_create_on(res.create_on); + resp_status->set_err_msg(res.err_msg); + + // 填充站点数组 + for (auto& st : res.stations) + { + auto* pb_st = resp_status->add_stations(); + pb_st->set_id(st.id); + pb_st->set_type(st.type); + pb_st->set_x(st.x); + pb_st->set_y(st.y); + pb_st->set_r(st.r); + pb_st->set_desc(st.desc); + pb_st->set_executor(st.executor); + pb_st->set_prepoint(st.prepoint); + pb_st->set_recfile(st.recfile); + pb_st->set_spin(st.spin); + pb_st->set_use_down_pgv(st.use_down_pgv); + } + + auto* header = response->mutable_header(); + header->set_success(true); + header->set_error_message(""); + setCurrentTimestamp(header->mutable_timestamp()); + + LOG(INFO) << "QueryStationList finish dev_id:" << dev_id + << " station_count=" << res.stations.size() + << " ret_code=" << res.ret_code; + return grpc::Status::OK; + } + catch (std::exception& e) + { + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message(e.what()); + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } + } + + + grpc::Status gRPCAGVServiceImpl::RobotGoTargetList( + grpc::ServerContext* context, + const api::RobotGoTargetListCommand_Request* request, + api::RobotGoTargetListCommand_Feedback* response) + { + try { + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (RobotGoTargetList): target device_id=" << dev_id; + const auto agv_dev = dmgr_.getDevice(dev_id); + + device::AbstractAgv::GoTargetListReq req; + const auto& pb_list = request->data().move_task_list(); + for (const auto& pb_item : pb_list) + { + device::AbstractAgv::MoveTaskItem task; + task.task_id = pb_item.task_id(); + task.source_id = pb_item.source_id(); + task.id = pb_item.id(); + task.operation = pb_item.operation(); + task.jack_height = pb_item.jack_height(); + req.move_task_list.push_back(task); + } + + device::AbstractAgv::GoTargetListRes res; + agv_dev->robotGoTargetList(res, req); + + auto* resp_status = response->mutable_status(); + resp_status->set_ret_code(res.ret_code); + resp_status->set_create_on(res.create_on); + resp_status->set_err_msg(res.err_msg); + + auto* header = response->mutable_header(); + header->set_success(true); + header->set_error_message(""); + setCurrentTimestamp(header->mutable_timestamp()); + + LOG(INFO) << "RobotGoTargetList finish dev_id:" << dev_id + << " task_count=" << req.move_task_list.size() + << " ret_code=" << res.ret_code; + return grpc::Status::OK; + } + catch (std::exception& e) + { + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message(e.what()); + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } + } + + + + + grpc::Status gRPCAGVServiceImpl::RobotStatusTask( + grpc::ServerContext* context, + const api::RobotStatusTaskCommand_Request* request, + api::RobotStatusTaskCommand_Feedback* response) + { + try { + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (RobotStatusTask): id=" << dev_id; + const auto agv_dev = dmgr_.getDevice(dev_id); + + // 嵌套结构体 必须加 AbstractAgv:: + device::AbstractAgv::RobotStatusTaskReq req; + if (request->has_data()) + { + req.simple = request->data().simple(); + } + + device::AbstractAgv::RobotStatusTaskRes res; + agv_dev->robotStatusTask(res, req); + + auto* out_data = response->mutable_data(); + out_data->set_ret_code(res.ret_code); + out_data->set_create_on(res.create_on); + out_data->set_err_msg(res.err_msg); + + out_data->set_task_status(res.task_status); + out_data->set_task_type(res.task_type); + out_data->set_target_id(res.target_id); + out_data->set_move_status_info(res.move_status_info); + + for (double p : res.target_point) + out_data->add_target_point(p); + for (auto& s : res.finished_path) + out_data->add_finished_path(s); + for (auto& s : res.unfinished_path) + out_data->add_unfinished_path(s); + + for (auto& c : res.containers) + { + auto* pc = out_data->add_containers(); + pc->set_container_name(c.container_name); + pc->set_desc(c.desc); + pc->set_goods_id(c.goods_id); + pc->set_has_goods(c.has_goods); + } + + auto* header = response->mutable_header(); + header->set_success(true); + header->set_error_message(""); + setCurrentTimestamp(header->mutable_timestamp()); + + LOG(INFO) << "RobotStatusTask device_id:" << dev_id << " task_status: " << res.task_status; + return grpc::Status::OK; + } + catch (std::exception &e) + { + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message(e.what()); + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } + } + + + +} \ No newline at end of file diff --git a/cmvr-es/service/grpc/src/server_runner.cpp b/cmvr-es/service/grpc/src/server_runner.cpp index ba3d0ce1..32726748 100644 --- a/cmvr-es/service/grpc/src/server_runner.cpp +++ b/cmvr-es/service/grpc/src/server_runner.cpp @@ -22,6 +22,7 @@ #include "service/grpc/include/grpc_hlc_service.h" #include "json/json.h" #include +#include "service/grpc/include/grpc_agv_service.h" using namespace cmvr::service; ServerRunner::ServerRunner() = default; @@ -118,6 +119,7 @@ void ServerRunner::threadMain() { auto biohand_service = std::make_unique(); auto humanoid_robot_service = std::make_unique(); auto hlc_service = std::make_unique(); + auto agv_service = std::make_unique(); grpc::ServerBuilder builder; builder.AddListeningPort(local_address, grpc::InsecureServerCredentials()); @@ -129,6 +131,7 @@ void ServerRunner::threadMain() { builder.RegisterService(biohand_service.get()); builder.RegisterService(humanoid_robot_service.get()); builder.RegisterService(hlc_service.get()); + builder.RegisterService(agv_service.get()); auto local_server = builder.BuildAndStart(); if (!local_server) { diff --git a/protos/cmvr/api/agv_command.proto b/protos/cmvr/api/agv_command.proto new file mode 100644 index 00000000..d2a4fad9 --- /dev/null +++ b/protos/cmvr/api/agv_command.proto @@ -0,0 +1,424 @@ +syntax = "proto3"; + +import "cmvr/api/common.proto"; + +package cmvr.api; + +// AGV状态信息 +message AgvStatusInfo { + optional string id = 1; // AGV ID + optional string vehicle_id = 2; // 车辆ID + optional string version = 3; // 软件版本 + optional string model = 4; // 型号 + optional string dsp_version = 5; // DSP版本 + optional string current_ip = 6; // 当前IP地址 + optional string mac = 7; // MAC地址 + optional int32 rssi = 8; // 信号强度 + optional int32 ret_code = 9; // 返回码 + optional string err_msg = 10; // 错误信息 +} + +// 获取AGV状态命令 +message GetAgvStatusInfoCommand { + message Request { + CommandHeader.Request header = 1; + } + + message Feedback { + CommandHeader.Feedback header = 1; + AgvStatusInfo status = 2; + } +} + + +// ===================== 电池状态查询 ===================== +message AgvBatteryStatus { + optional double battery_level = 1; // 电池电量 [0,1] + optional double battery_temp = 2; // 电池温度 ℃ + optional bool charging = 3; // 是否正在充电 + optional double voltage = 4; // 电压 V + optional double current = 5; // 电流 A + optional double max_charge_voltage = 6; // 最大充电电压,-1=不支持 + optional double max_charge_current = 7; // 最大充电电流,-1=不支持 + optional bool manual_charge = 8; // 手动充电状态 + optional bool auto_charge = 9; // 自动充电状态 + optional int32 battery_cycle = 10; // 电池循环次数 + optional string battery_user_data = 11; // 电池自定义数据 + optional string extra = 12; // 客户自定义数据 + optional int32 ret_code = 13; // 错误码 + optional string create_on = 14; // 时间戳 + optional string err_msg = 15; // 错误信息 +} + +message RobotStatusBatteryRequestData { + optional bool simple = 1; // true:仅简单数据 false:全量数据,默认false +} + +message RobotStatusBatteryCommand { + message Request { + CommandHeader.Request header = 1; + RobotStatusBatteryRequestData data = 2; + } + message Feedback { + CommandHeader.Feedback header = 1; + AgvBatteryStatus status = 2; + } +} + + + +// ===================== 机器人位置查询 ===================== +message AgvRobotLocation { + optional double x = 1; // X坐标(m) + optional double y = 2; // Y坐标(m) + optional double angle = 3; // 姿态角(rad) + optional double confidence = 4; // 定位置信度[0,1] + optional string current_station = 5; // 当前最近站点ID + optional string last_station = 6; // 上一站点ID + optional int32 loc_method = 7; // 定位方式 + optional int32 ret_code = 8; // 错误码 + optional string create_on = 9; // 时间戳 + optional string err_msg = 10; // 错误信息 +} + +message RobotStatusLocCommand { + message Request { + CommandHeader.Request header = 1; + } + message Feedback { + CommandHeader.Feedback header = 1; + AgvRobotLocation status = 2; + } +} + + +// ===================== 地图下载 ===================== +message RobotConfigDownloadMapRequestData { + optional string map_name = 1; +} + +message AgvDownloadMapResult { + optional int32 ret_code = 1; + optional string create_on = 2; + optional string err_msg = 3; + optional string map_content = 4; +} + +message RobotConfigDownloadMapCommand { + message Request { + CommandHeader.Request header = 1; + RobotConfigDownloadMapRequestData data = 2; + } + message Feedback { + CommandHeader.Feedback header = 1; + AgvDownloadMapResult status = 2; + } +} + + +// 单条地图文件信息 +message MapFileInfo { + optional string name = 1; + optional string modified = 2; + optional int64 size = 3; +} + +// 地图查询响应 +message AgvMapStatus { + optional string current_map = 1; + optional string current_map_md5 = 2; + repeated string maps = 3; + repeated MapFileInfo map_files_info = 4; + optional int32 ret_code = 5; + optional string create_on = 6; + optional string err_msg = 7; +} + +// 查询地图状态命令 +message RobotStatusMapCommand { + message Request { + CommandHeader.Request header = 1; + } + message Feedback { + CommandHeader.Feedback header = 1; + AgvMapStatus status = 2; + } +} + + +// ===================== 上传地图 ===================== +message RobotConfigUploadMapRequestData { + optional string map_content = 1; +} + +message AgvUploadMapResult { + optional int32 ret_code = 1; + optional string create_on = 2; + optional string err_msg = 3; +} + +message RobotConfigUploadMapCommand { + message Request { + CommandHeader.Request header = 1; + RobotConfigUploadMapRequestData data = 2; + } + message Feedback { + CommandHeader.Feedback header = 1; + AgvUploadMapResult status = 2; + } +} + + +// ===================== 抢占控制权 ====================== +// 抢占控制权 请求参数 +message RobotConfigLockRequestData { + // 控制权抢占者名称,必填 + optional string nick_name = 1; +} + +// 抢占控制权 返回结果 +message AgvLockResult { + // API错误码 + optional int32 ret_code = 1; + // API上传时间戳 + optional string create_on = 2; + // 错误描述信息 + optional string err_msg = 3; +} + +// 抢占控制权 命令封装 +message RobotConfigLockCommand { + message Request { + CommandHeader.Request header = 1; + // 请求业务参数 + RobotConfigLockRequestData data = 2; + } + message Feedback { + CommandHeader.Feedback header = 1; + // 抢占控制权响应数据 + AgvLockResult status = 2; + } +} + + + +// ===================== 查询当前控制权所有者 ====================== +// 查询当前控制权所有者响应结构体 +message AgvCurrentLockStatus { + // 当前控制权是否被抢占 + optional bool locked = 1; + // 抢占方IP + optional string ip = 2; + // 抢占方端口 + optional int32 port = 3; + // 抢占者类型 0=default 2=roboshop 0xDD=srd + optional uint32 type = 4; + // 抢占者昵称 + optional string nick_name = 5; + // 抢占时间戳 单位秒 + optional int64 time_t = 6; + // 抢占方描述信息 + optional string desc = 7; + // 接口错误码 + optional int32 ret_code = 8; + // 接口时间戳 + optional string create_on = 9; + // 错误信息 + optional string err_msg = 10; +} + + + +// 查询当前控制权指令 +message RobotStatusCurrentLockCommand { + message Request { + CommandHeader.Request header = 1; + } + message Feedback { + CommandHeader.Feedback header = 1; + AgvCurrentLockStatus status = 2; + } +} + + +//==================== 开环速度运动控制 命令码:2010(0x07DA) ==================== +// 下发指令会强制取消当前正在执行的自动导航任务 +// 多舵轮设备仅解析vx/vy/w;steer、real_steer仅单舵轮设备生效 +// vx:X轴速度 m/s;vy:Y轴速度 m/s;w:旋转角速度 rad/s,逆时针为正、顺时针为负 +// steer:单舵轮档位控制 -2回零 / ±15°增减;real_steer:目标舵角rad,优先级高于steer +// duration:持续时间ms,0=永久运行;缺省读取全局参数ControlMotionDuration(默认500ms) +message RobotMotionControlRequestData { + optional double vx = 1; + optional double vy = 2; + optional double w = 3; + optional double steer = 4; + optional double real_steer = 5; + optional int64 duration = 6; +} + +message RobotMotionControlResult { + optional int32 ret_code = 1; + optional string create_on = 2; + optional string err_msg = 3; +} + +message RobotMotionControlCommand { + message Request { + CommandHeader.Request header = 1; + RobotMotionControlRequestData data = 2; + } + message Feedback { + CommandHeader.Feedback header = 1; + RobotMotionControlResult status = 2; + } +} + + +//==================== 切换载入地图 命令码:2022(0x07E6) ==================== +// 切换指定地图,合法地图会更新设备current_map字段 +message RobotLoadMapRequestData { + optional string map_name = 1; // 地图名,仅字母数字-_,不可缺省 +} + +message RobotLoadMapResult { + optional int32 ret_code = 1; + optional string create_on = 2; + optional string err_msg = 3; +} + +message RobotLoadMapCommand { + message Request { + CommandHeader.Request header = 1; + RobotLoadMapRequestData data = 2; + } + message Feedback { + CommandHeader.Feedback header = 1; + RobotLoadMapResult status = 2; + } +} + + +//==================== 查询地图载入状态 命令码:1022(0x03FE) ==================== +// loadmap_status:0失败 1成功 2载入中;LOADING状态不可重定位 +message RobotQueryLoadMapStatusResult { + optional int32 loadmap_status = 1; + optional int32 ret_code = 2; + optional string create_on = 3; + optional string err_msg = 4; +} + +message RobotQueryLoadMapStatusCommand { + message Request { + CommandHeader.Request header = 1; + } + message Feedback { + CommandHeader.Feedback header = 1; + RobotQueryLoadMapStatusResult status = 2; + } +} + + + +//==================== 查询当前地图所有站点信息 命令码:1301(0x0515) ==================== +message StationItem { + optional string id = 1; + optional string type = 2; + optional double x = 3; + optional double y = 4; + optional double r = 5; + optional string desc = 6; + optional string executor = 7; + optional string prepoint = 8; + optional string recfile = 9; + optional bool spin = 10; + optional bool use_down_pgv = 11; +} + +message QueryStationListResult { + repeated StationItem stations = 1; + optional int32 ret_code = 2; + optional string create_on = 3; + optional string err_msg = 4; +} + +message QueryStationListCommand { + message Request { + CommandHeader.Request header = 1; + } + message Feedback { + CommandHeader.Feedback header = 1; + QueryStationListResult status = 2; + } +} + +//==================== 指定路径导航 命令码:3066(0x0BFA) ==================== +// 单段导航任务单元 +message MoveTaskItem { + optional string task_id = 1; + optional string source_id = 2; + optional string id = 3; + optional string operation = 4; + optional double jack_height = 5; +} + +// 指定路径导航请求体 +message RobotGoTargetListRequestData { + repeated MoveTaskItem move_task_list = 1; +} + +// 指定路径导航返回结果 +message RobotGoTargetListResult { + optional int32 ret_code = 1; + optional string create_on = 2; + optional string err_msg = 3; +} + +message RobotGoTargetListCommand { + message Request { + CommandHeader.Request header = 1; + RobotGoTargetListRequestData data = 2; + } + message Feedback { + CommandHeader.Feedback header = 1; + RobotGoTargetListResult status = 2; + } +} + + +//==================== 查询当前导航状态 robot_status_task_req 1020(0x03FC) 应答0x2B0C ==================== +message QueryCurrentNavReqData { + optional bool simple = 1; +} + +message NavContainerItem { + optional string container_name = 1; + optional string desc = 2; + optional string goods_id = 3; + optional bool has_goods = 4; +} + +message QueryCurrentNavResData { + optional int32 task_status = 1; + optional int32 task_type = 2; + optional string target_id = 3; + repeated double target_point = 4; + repeated string finished_path = 5; + repeated string unfinished_path = 6; + optional string move_status_info = 7; + repeated NavContainerItem containers = 8; + + optional int32 ret_code = 9; + optional string create_on = 10; + optional string err_msg = 11; +} + +message RobotStatusTaskCommand { + message Request { + CommandHeader.Request header = 1; + QueryCurrentNavReqData data = 2; + } + message Feedback { + CommandHeader.Feedback header = 1; + QueryCurrentNavResData data = 2; + } +} diff --git a/protos/cmvr/api/agv_service.proto b/protos/cmvr/api/agv_service.proto new file mode 100644 index 00000000..6434904d --- /dev/null +++ b/protos/cmvr/api/agv_service.proto @@ -0,0 +1,60 @@ +syntax = "proto3"; + +import "cmvr/api/agv_command.proto"; + +package cmvr.api; + +service AgvService { + // 基本控制 + rpc GetStatusInfo(GetAgvStatusInfoCommand.Request) returns (GetAgvStatusInfoCommand.Feedback); + + rpc GetBatteryStatus(RobotStatusBatteryCommand.Request) returns (RobotStatusBatteryCommand.Feedback); + + rpc GetRobotLocation(RobotStatusLocCommand.Request) returns (RobotStatusLocCommand.Feedback); + + rpc RobotConfigDownloadMap(RobotConfigDownloadMapCommand.Request) returns (RobotConfigDownloadMapCommand.Feedback); + + + rpc GetMapStatus(RobotStatusMapCommand.Request) returns (RobotStatusMapCommand.Feedback); + + + rpc RobotConfigUploadMap(RobotConfigUploadMapCommand.Request) returns (RobotConfigUploadMapCommand.Feedback); + + // 抢占机器人控制权 + rpc RobotConfigLock(RobotConfigLockCommand.Request) returns (RobotConfigLockCommand.Feedback); + + // 查询当前控制权所有者 + rpc GetCurrentLockStatus(RobotStatusCurrentLockCommand.Request) returns (RobotStatusCurrentLockCommand.Feedback); + + // 开环速度运动控制 + rpc RobotMotionControl(RobotMotionControlCommand.Request) returns (RobotMotionControlCommand.Feedback); + + // 切换载入地图 命令码2022 + rpc RobotLoadMap(RobotLoadMapCommand.Request) returns (RobotLoadMapCommand.Feedback); + + + // 查询地图载入状态 命令码1022 + rpc QueryLoadMapStatus(RobotQueryLoadMapStatusCommand.Request) returns (RobotQueryLoadMapStatusCommand.Feedback); + + + // 查询当前地图站点列表 命令码1301 + rpc QueryStationList(QueryStationListCommand.Request) returns (QueryStationListCommand.Feedback); + + + // 指定路径导航 命令码3066 + rpc RobotGoTargetList(RobotGoTargetListCommand.Request) returns (RobotGoTargetListCommand.Feedback); + + // 查询当前导航状态 1020 robot_status_task_req + rpc RobotStatusTask(RobotStatusTaskCommand.Request) returns (RobotStatusTaskCommand.Feedback); + + + + + + + + + + +} + diff --git a/protos/cmvr/config/agv_config/agv_config.proto b/protos/cmvr/config/agv_config/agv_config.proto new file mode 100644 index 00000000..cb972f1d --- /dev/null +++ b/protos/cmvr/config/agv_config/agv_config.proto @@ -0,0 +1,20 @@ +// camera_config.proto - 所有配置都在这里 +syntax = "proto3"; +package cmvr.config; + + +message AGVsrc1100Config{ + string id = 1; + string ip = 2; + bool enable = 3; + int32 port_status = 4; // 19204 状态查询 + int32 port_control = 5; // 19205 控制API + int32 port_nav = 6; // 19206 导航API + int32 port_config = 7; // 19207 配置API + int32 port_other = 8; // 19210 其他API + int32 port_push = 9; // 19301 推送API +} + +message AGVConfig{ + repeated AGVsrc1100Config src1100_agvs = 1; +} From 3edbd1dc34f8d4e9d6c40ff937452deb7e6e880c Mon Sep 17 00:00:00 2001 From: linbo <1034003879@qq.com> Date: Wed, 17 Jun 2026 14:57:39 +0800 Subject: [PATCH 5/9] update server config --- cmvr-es/main.cpp | 2 +- cmvr-es/service/grpc/src/server_runner.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cmvr-es/main.cpp b/cmvr-es/main.cpp index 24f189a5..c5531829 100644 --- a/cmvr-es/main.cpp +++ b/cmvr-es/main.cpp @@ -9,7 +9,7 @@ #include #include "service/grpc/include/server_runner.h" #include "utils/base/include/logger.h" -#include "common/utils/config_helper/include/config_helper.h" + using namespace cmvr::service; int main(int argc, char* argv[]) { std::string config_path; diff --git a/cmvr-es/service/grpc/src/server_runner.cpp b/cmvr-es/service/grpc/src/server_runner.cpp index 32726748..c2817179 100644 --- a/cmvr-es/service/grpc/src/server_runner.cpp +++ b/cmvr-es/service/grpc/src/server_runner.cpp @@ -23,6 +23,7 @@ #include "json/json.h" #include #include "service/grpc/include/grpc_agv_service.h" +#include "common/utils/config_helper/include/config_helper.h" using namespace cmvr::service; ServerRunner::ServerRunner() = default; @@ -108,6 +109,7 @@ void ServerRunner::threadMain() { auto dmgr_cfg = cfg_.getChild("DeviceManager"); DeviceManager::getInstance(dmgr_cfg); + cmvr::ConfigHelper::getServerConfig(servercfg_); std::string port = servercfg_.grpc_server().port(); std::string local_address = "0.0.0.0:" + port; From 2e01a6035ad8be65495b3d469c8b2d94a5eb2851 Mon Sep 17 00:00:00 2001 From: linbo <1034003879@qq.com> Date: Wed, 17 Jun 2026 16:02:02 +0800 Subject: [PATCH 6/9] update robot config --- cmvr-es/device_manager/src/device_manager.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cmvr-es/device_manager/src/device_manager.cpp b/cmvr-es/device_manager/src/device_manager.cpp index 581a0aa6..60a8c824 100644 --- a/cmvr-es/device_manager/src/device_manager.cpp +++ b/cmvr-es/device_manager/src/device_manager.cpp @@ -312,6 +312,24 @@ void DeviceManager::init_devices_() { LOG(INFO) << "[DeviceManager]: Init Robot " << id << " Success"; } + // EYOU的先用新版的 + config::RobotConfig robot_config; + ConfigHelper::getRobotConfig(robot_config); + for (int i = 0; i < robot_config.ethercat_robots().size(); i++) { + auto robot = robot_config.ethercat_robots(i); + if (!robot.enable()) + continue; + auto id = robot.id(); + if (devices_.count(id)) { + LOG(ERROR) << "[DeviceManager]: Duplicate Robot Device ID" << id; + throw runtime_error("[DeviceManager]: Duplicate Robot Device ID" + id); + } + std::cout << "DexHand[" << i << "]: " << id << std::endl; + auto device = dev_factory_->createFromConfig(robot); + devices_[id] = device; + std::get>(devices_[id])->init(); + } + config::MicroPhoneConfig micro_phone_config; ConfigHelper::getMicroPhonesConfig(micro_phone_config); for (int i = 0; i < micro_phone_config.ffmpeg_microphones().size(); i++) { From b4468809daaa59d3fd1f6998a61e9e89ae248418 Mon Sep 17 00:00:00 2001 From: tankaitao <1767759995@qq.com> Date: Tue, 23 Jun 2026 16:44:03 +0800 Subject: [PATCH 7/9] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/agv_config/agv_config.pb.txt | 12 +- cmvr-es/devices/agv/abstract_agv.h | 891 +++- .../agv/agv_src1100/include/agv_src1100.h | 325 +- .../agv/agv_src1100/src/agv_src1100.cpp | 4405 ++++++++++------- .../service/grpc/include/grpc_agv_service.h | 333 +- cmvr-es/service/grpc/src/grpc_agv_service.cpp | 782 ++- protos/cmvr/api/agv_command.proto | 866 +++- protos/cmvr/api/agv_service.proto | 32 +- 8 files changed, 5190 insertions(+), 2456 deletions(-) diff --git a/cmvr-es/common/config/agv_config/agv_config.pb.txt b/cmvr-es/common/config/agv_config/agv_config.pb.txt index f62d19f2..e9c35fe1 100644 --- a/cmvr-es/common/config/agv_config/agv_config.pb.txt +++ b/cmvr-es/common/config/agv_config/agv_config.pb.txt @@ -1,12 +1,12 @@ src1100_agvs { id: "agv_src1100" ip: "192.168.192.5" - port_status: 19301 - port_control: 19301 - port_nav: 19301 - port_config: 19301 - port_other: 19301 - port_push: 19301 + port_status: 19204 + port_control: 19205 + port_nav: 19206 + port_config: 19207 + port_other: 19210 + port_push: 19302 diff --git a/cmvr-es/devices/agv/abstract_agv.h b/cmvr-es/devices/agv/abstract_agv.h index da3c1ec9..858e3973 100644 --- a/cmvr-es/devices/agv/abstract_agv.h +++ b/cmvr-es/devices/agv/abstract_agv.h @@ -1,6 +1,12 @@ // // Created by xtkuang on 2025/5/6. // +/** + * @file abstract_agv.h + * @brief AGV设备抽象基类定义,提供统一的接口规范和数据结构。 + * 所有具体AGV实现(如SRC-1100)需继承此类。 + * @note 本文件定义了大量与仙工API对应的结构体,用于状态查询、运动控制、导航任务等。 + */ #ifndef CMVR_ES_ABSTRACT_AGV_H #define CMVR_ES_ABSTRACT_AGV_H #pragma once @@ -21,416 +27,841 @@ namespace cmvr::device { +/** + * @brief AGV(自动导引车)设备的抽象基类 + * + * 继承自 AbstractDevice,定义了 AGV 设备通用的接口和行为, + * 包括状态管理、信息查询、运动控制、地图管理、导航任务等功能。 + * 所有具体 AGV 设备实现必须继承此类并实现其纯虚函数。 + */ class AbstractAgv : public AbstractDevice { public: + /** + * @brief AGV 设备运行状态枚举 + */ enum class Status { - CREATED, - INITIALIZED, - RUNNING, - PAUSED, - STOPPED, - FAULT + CREATED, ///< 已创建,未初始化 + INITIALIZED, ///< 已初始化,未运行 + RUNNING, ///< 运行中 + PAUSED, ///< 暂停 + STOPPED, ///< 已停止 + FAULT ///< 故障 }; - //查询机器人信息 - struct AgvStatusInfo { - std::string id; - std::string vehicle_id; - std::string robot_note; - std::string version; - std::string model; - std::string dsp_version; - std::string gyro_version; - std::string map_version; - std::string model_version; - std::string netprotocol_version; - std::string modbus_version; - std::string current_map; - std::string current_map_md5; - std::string model_md5; - std::string ssid; - int rssi = 0; - std::string ap_addr; - std::string current_ip; - std::string mac; - std::string echoid_type; - std::string echoid; - int ret_code = 0; - std::string err_msg; + // ==================== 状态与信息结构体 ==================== + + /** + * @brief 机器人基本信息查询结果 + * + * 对应仙工 API 1000 (0x03E8) 的响应字段 + */ + struct AgvStatusInfo { + std::string id; ///< 设备唯一标识 + std::string vehicle_id; ///< 车辆编号(如 "agv_001") + std::string robot_note; ///< 机器人备注(用户自定义) + std::string version; ///< 软件版本号 + std::string model; ///< 车型(如 "SRC-1100") + std::string dsp_version; ///< DSP 固件版本 + std::string gyro_version; ///< 陀螺仪版本 + std::string map_version; ///< 地图版本 + std::string model_version; ///< 模型版本 + std::string netprotocol_version; ///< 网络协议版本 + std::string modbus_version; ///< Modbus 协议版本 + std::string current_map; ///< 当前加载的地图名称 + std::string current_map_md5; ///< 当前地图的 MD5 校验值 + std::string model_md5; ///< 模型文件 MD5 + std::string ssid; ///< 当前连接的 Wi-Fi SSID + int rssi = 0; ///< Wi-Fi 信号强度 (RSSI, 0~100) + std::string ap_addr; ///< 接入点 MAC 地址 + std::string current_ip; ///< 当前 IP 地址 + std::string mac; ///< 设备 MAC 地址 + std::string echoid_type; ///< 回音 ID 类型 + std::string echoid; ///< 回音 ID + int ret_code = 0; ///< 返回码,0 表示成功,非0 表示错误 + std::string err_msg; ///< 错误信息(失败时填充) + /** + * @brief 检查信息是否有效(ID 非空且返回码为 0) + */ bool isValid() const { return !id.empty() && ret_code == 0; } }; - - // 电池状态信息 + /** + * @brief 电池状态信息 + * + * 对应仙工 API 1007 (0x03EF) 的响应字段 + */ struct BatteryStatus { - double battery_level = 0.0; - double battery_temp = 0.0; - bool charging = false; - double voltage = 0.0; - double current = 0.0; - double max_charge_voltage = -1.0; - double max_charge_current = -1.0; - bool manual_charge = false; - bool auto_charge = false; - int32_t battery_cycle = 0; - std::string battery_user_data; - std::string extra; - int32_t ret_code = 0; - std::string create_on; - std::string err_msg; + double battery_level = 0.0; ///< 电池电量百分比 (0~100) + double battery_temp = 0.0; ///< 电池温度(摄氏度) + bool charging = false; ///< 是否正在充电 + double voltage = 0.0; ///< 当前电压(V) + double current = 0.0; ///< 当前电流(A) + double max_charge_voltage = -1.0; ///< 最大充电电压(-1 表示未知) + double max_charge_current = -1.0; ///< 最大充电电流(-1 表示未知) + bool manual_charge = false; ///< 是否连接手动充电器(仅 SRC-2000) + bool auto_charge = false; ///< 是否连接自动充电桩(仅 SRC-2000) + int32_t battery_cycle = 0; ///< 电池循环次数(从 BMS 读取,可能不准确) + std::string battery_user_data; ///< 电池用户自定义数据(字符串) + std::string extra; ///< 额外扩展字段(预留) + int32_t ret_code = 0; ///< 返回码,0 表示成功 + std::string create_on; ///< 数据创建时间戳(ISO 8601) + std::string err_msg; ///< 错误信息 }; - - // 机器人位置信息 + /** + * @brief 机器人位置信息 + * + * 对应仙工 API 1004 (0x03EC) 的响应字段 + */ struct RobotLocation { - double x = 0.0; - double y = 0.0; - double angle = 0.0; - double confidence = 0.0; - std::string current_station; - std::string last_station; - int32_t loc_method = 0; - int32_t ret_code = 0; - std::string create_on; - std::string err_msg; + double x = 0.0; ///< 世界坐标系 X 坐标(米) + double y = 0.0; ///< 世界坐标系 Y 坐标(米) + double angle = 0.0; ///< 朝向角(弧度),逆时针为正 + double confidence = 0.0; ///< 定位置信度(0~1) + std::string current_station; ///< 当前所在站点 ID(若在站点上) + std::string last_station; ///< 上一个经过的站点 ID + int32_t loc_method = 0; ///< 定位方法(0: 自然轮廓, 1: 反光柱, 2: 二维码, 3: 里程计, ...) + int32_t ret_code = 0; ///< 返回码,0 表示成功 + std::string create_on; ///< 数据创建时间戳 + std::string err_msg; ///< 错误信息 }; - // 地图下载结果 + /** + * @brief 地图下载结果 + * + * 对应仙工 API 4011 (0x0FAB) 的响应 + */ struct DownloadMapResult { - std::string map_name; - std::string map_content; - int32_t ret_code = 0; - std::string create_on; - std::string err_msg; + std::string map_name; ///< 地图名称 + std::string map_content; ///< 地图文件内容(JSON 字符串) + int32_t ret_code = 0; ///< 返回码,0 表示成功 + std::string create_on; ///< 数据创建时间戳 + std::string err_msg; ///< 错误信息 }; - - - // 单地图文件信息 + /** + * @brief 单个地图文件信息 + */ struct MapFileInfo { - std::string name; - std::string modified; - int64_t size = 0; + std::string name; ///< 文件名 + std::string modified; ///< 最后修改时间 + int64_t size = 0; ///< 文件大小(字节) }; - // 地图整体状态 + /** + * @brief 地图整体状态(当前地图、所有地图列表及文件详情) + * + * 对应仙工 API 1300 (0x0514) 的响应 + */ struct MapStatus { - std::string current_map; - std::string current_map_md5; - std::vector maps; - std::vector map_files_info; - int32_t ret_code = 0; - std::string create_on; - std::string err_msg; + std::string current_map; ///< 当前加载的地图名称 + std::string current_map_md5; ///< 当前地图的 MD5 + std::vector maps; ///< 所有地图名称列表 + std::vector map_files_info; ///< 每个地图文件的详细信息 + int32_t ret_code = 0; ///< 返回码,0 表示成功 + std::string create_on; ///< 数据创建时间戳 + std::string err_msg; ///< 错误信息 }; - - // 上传地图结果 + /** + * @brief 上传地图结果 + * + * 对应仙工 API 4010 (0x0FAA) 的响应 + */ struct UploadMapResult { - int32_t ret_code = 0; - std::string create_on; - std::string err_msg; + int32_t ret_code = 0; ///< 返回码,0 表示成功 + std::string create_on; ///< 数据创建时间戳 + std::string err_msg; ///< 错误信息 }; - // 抢占控制权返回结果 + /** + * @brief 抢占控制权返回结果 + * + * 对应仙工 API 4005 (0x0FA5) 的响应 + */ struct LockResult { - int32_t ret_code = 0; - std::string create_on; - std::string err_msg; + int32_t ret_code = 0; ///< 返回码,0 表示成功 + std::string create_on; ///< 数据创建时间戳 + std::string err_msg; ///< 错误信息 }; - - // 当前控制权信息 + /** + * @brief 当前控制权信息 + * + * 对应仙工 API 1060 (0x0424) 的响应 + */ struct CurrentLockStatus { - bool locked = false; - std::string ip; - int32_t port = 0; - uint8_t type = 0; - std::string nick_name; - int64_t time_t = 0; - std::string desc; - int32_t ret_code = 0; - std::string create_on; - std::string err_msg; + bool locked = false; ///< 是否被锁定(有控制权持有者) + std::string ip; ///< 持有者 IP 地址 + int32_t port = 0; ///< 持有者端口 + uint8_t type = 0; ///< 持有者类型(0: 未知, 1: 调度系统, 2: 示教器, ...) + std::string nick_name; ///< 持有者昵称(自定义标识) + int64_t time_t = 0; ///< 锁定时间戳(Unix 秒) + std::string desc; ///< 描述信息 + int32_t ret_code = 0; ///< 返回码,0 表示成功 + std::string create_on; ///< 数据创建时间戳 + std::string err_msg; ///< 错误信息 }; + // ==================== 运动控制相关结构体 ==================== - //==================== 新增:运动控制结构体 ==================== - // 开环运动下发参数结构体 + /** + * @brief 开环运动下发参数 + * + * 对应仙工 API 2010 (0x07DA) 的请求参数 + * 注意:多舵轮设备仅 vx/vy/w 生效,steer/real_steer 仅单舵轮设备有效。 + */ struct MotionCtrlReq { - double vx = 0.0; - double vy = 0.0; - double w = 0.0; - double steer = 0.0; - double real_steer = 0.0; - int64_t duration = -1; // -1 代表缺省,使用设备默认时长 + double vx = 0.0; ///< 机器人坐标系 X 方向线速度(m/s) + double vy = 0.0; ///< 机器人坐标系 Y 方向线速度(m/s) + double w = 0.0; ///< 角速度(rad/s),逆时针为正 + double steer = 0.0; ///< 舵轮转向角(仅单舵轮,单位 rad) + double real_steer = 0.0; ///< 实际舵轮角度(反馈,仅单舵轮) + int64_t duration = -1; ///< 运动持续时间(毫秒),-1 表示一直运动直到下次指令 }; - // 运动控制返回结果结构体 + /** + * @brief 运动控制返回结果 + */ struct MotionCtrlRes { - int ret_code = 0; - std::string create_on; - std::string err_msg; + int ret_code = 0; ///< 返回码,0 表示成功 + std::string create_on; ///< 数据创建时间戳 + std::string err_msg; ///< 错误信息 }; - - // 切换地图入参 + /** + * @brief 切换地图入参 + * + * 对应仙工 API 2022 (0x07E6) 的请求 + */ struct LoadMapReq { - std::string map_name; + std::string map_name; ///< 目标地图名称(必须已存在于机器人中) }; - // 切换地图返回结果 + /** + * @brief 切换地图返回结果 + */ struct LoadMapRes { - int ret_code = 0; - std::string create_on; - std::string err_msg; + int ret_code = 0; ///< 返回码,0 表示成功 + std::string create_on; ///< 数据创建时间戳 + std::string err_msg; ///< 错误信息 }; - // 查询地图载入状态返回 + /** + * @brief 查询地图载入状态返回 + * + * 对应仙工 API 1022 (0x03FE) 的响应 + */ struct QueryLoadMapStatusRes { - int32_t loadmap_status = 0; //0失败 1成功 2载入中 - int ret_code = 0; - std::string create_on; - std::string err_msg; + int32_t loadmap_status = 0; ///< 地图加载状态:0=失败, 1=成功, 2=载入中 + int ret_code = 0; ///< 返回码,0 表示成功 + std::string create_on; ///< 数据创建时间戳 + std::string err_msg; ///< 错误信息 }; - - // 单个站点信息 + /** + * @brief 单个站点信息 + * + * 对应仙工 API 1301 (0x0515) 响应中的单个站点对象 + */ struct StationItem { - std::string id; - std::string type; - double x = 0.0; - double y = 0.0; - double r = 0.0; - std::string desc; - std::string executor; - std::string prepoint; - std::string recfile; - bool spin = false; - bool use_down_pgv = false; + std::string id; ///< 站点 ID(唯一标识) + std::string type; ///< 站点类型(如 "LocationMark", "ChargePoint", "ActionPoint") + double x = 0.0; ///< 站点 X 坐标(米) + double y = 0.0; ///< 站点 Y 坐标(米) + double r = 0.0; ///< 站点朝向角(弧度) + std::string desc; ///< 描述信息 + std::string executor; ///< 执行器信息(用于动作) + std::string prepoint; ///< 前置点 ID(若需要先到前置点) + std::string recfile; ///< 识别文件路径(用于视觉识别) + bool spin = false; ///< 是否允许自旋(原地旋转) + bool use_down_pgv = false; ///< 是否使用下视 PGV 二维码 }; - // 查询站点列表返回结果 + /** + * @brief 查询站点列表返回结果 + */ struct QueryStationRes { - std::vector stations; - int ret_code = 0; - std::string create_on; - std::string err_msg; + std::vector stations; ///< 站点列表 + int ret_code = 0; ///< 返回码,0 表示成功 + std::string create_on; ///< 数据创建时间戳 + std::string err_msg; ///< 错误信息 }; - - // 单段导航任务 + /** + * @brief 单段导航任务(用于指定路径导航) + * + * 对应仙工 API 3066 (0x0BFA) 中 move_task_list 的每个元素 + */ struct MoveTaskItem { - std::string task_id; - std::string source_id; - std::string id; - std::string operation; - double jack_height = 0.0; + std::string task_id; ///< 任务 ID(必须全局唯一) + std::string source_id; ///< 起始站点 ID + std::string id; ///< 目标站点 ID + std::string operation; ///< 到达后执行的动作(如 "JackLoad", "WaitDI", 等) + double jack_height = 0.0; ///< 顶升高度(当 operation 为顶升相关时使用) }; - // 指定路径导航入参 + /** + * @brief 指定路径导航入参(多个导航段) + * + * 对应仙工 API 3066 的请求数据区 + */ struct GoTargetListReq { - std::vector move_task_list; + std::vector move_task_list; ///< 导航任务序列(必须按顺序且相邻站点间有直接路径) }; - // 指定路径导航返回 + /** + * @brief 指定路径导航返回结果 + */ struct GoTargetListRes { - int ret_code = 0; - std::string create_on; - std::string err_msg; + int ret_code = 0; ///< 返回码,0 表示成功 + std::string create_on; ///< 数据创建时间戳 + std::string err_msg; ///< 错误信息 }; - - // 导航料箱信息 + // ==================== 导航料箱信息 ==================== + /** + * @brief 导航任务中涉及的料箱信息 + */ struct NavContainerItem { - std::string container_name; - std::string desc; - std::string goods_id; - bool has_goods = false; + std::string container_name; ///< 料箱名称 + std::string desc; ///< 描述 + std::string goods_id; ///< 绑定的货物 ID + bool has_goods = false; ///< 是否已绑定货物 }; - // 1020 查询当前导航入参 - struct RobotStatusTaskReq + // ==================== 1020 实时导航查询 ==================== + /** + * @brief 1020 实时导航查询入参 + * + * 对应仙工 API 1020 (0x03FC) + */ + struct RobotStatusTaskCurrentReq { - bool simple = false; + bool simple = false; ///< 若为 true,只返回 task_status 字段 }; - // 1020 查询当前导航出参 - struct RobotStatusTaskRes + /** + * @brief 1020 实时导航查询出参 + * + * 对应仙工 API 1020 的响应 + */ + struct RobotStatusTaskCurrentRes { - int task_status = 0; - int task_type = 0; - std::string target_id; - std::vector target_point; - std::vector finished_path; - std::vector unfinished_path; - std::string move_status_info; - std::vector containers; + int task_status = 0; ///< 任务状态:0=NONE, 1=WAITING, 2=RUNNING, 3=SUSPENDED, 4=COMPLETED, 5=FAILED, 6=CANCELED + int task_type = 0; ///< 任务类型:0=无, 1=自由导航到坐标, 2=自由导航到站点, 3=路径导航到站点, 7=平动转动, 100=其他 + std::string target_id; ///< 目标站点 ID(若 task_type 为 2 或 3) + std::vector target_point; ///< 目标坐标 [x, y, r](若 task_type 为 1) + std::vector finished_path; ///< 已经过的站点列表 + std::vector unfinished_path; ///< 尚未经过的站点列表 + std::string move_status_info; ///< 导航附加信息(如错误描述) + std::vector containers; ///< 料箱信息(若有) + int ret_code = 0; ///< 返回码,0 表示成功 + std::string create_on; ///< 数据创建时间戳 + std::string err_msg; ///< 错误信息 + }; + // ==================== 1110 批量任务查询 ==================== + /** + * @brief 单条任务状态项 + */ + struct SingleTaskStatusItem + { + std::string task_id; ///< 任务 ID + int status = 0; ///< 状态码(同上述 task_status) + int type = 0; ///< 任务类型(同上述 task_type) + }; + + /** + * @brief 任务状态包(包含最近完成和所有未完成的任务) + * + * 对应仙工 API 1110 (0x0456) 的响应中的 task_status_package + */ + struct TaskStatusPackage + { + std::string closest_target; ///< 最近站点 ID + std::string source_name; ///< 起点站点名称 + std::string target_name; ///< 终点站点名称 + double percentage = 0.0; ///< 任务进度百分比(0~100) + double distance = 0.0; ///< 剩余距离(米) + std::string info; ///< 附加信息 + std::vector task_status_list; ///< 所有任务状态列表 + }; + + /** + * @brief 1110 批量查询任务入参 + */ + struct QueryTaskStatusPackageReq + { + std::vector task_ids; ///< 要查询的任务 ID 列表,若为空则查询所有未完成+最近一条完成 + }; + + /** + * @brief 1110 批量查询任务出参 + */ + struct QueryTaskStatusPackageRes + { + TaskStatusPackage task_status_package; ///< 任务状态包 + int ret_code = 0; ///< 返回码,0 表示成功 + std::string create_on; ///< 数据创建时间戳 + std::string err_msg; ///< 错误信息 + }; + + // ==================== DI / DO / 音频 / PGV / 脚本等通用类型 ==================== + /** + * @brief DI 点位信息 + */ + struct DIItem + { + int id = 0; ///< DI 编号 + bool status = false; ///< 状态(true=高电平, false=低电平) + }; + /** + * @brief DO 点位信息 + */ + struct DOItem + { + int id = 0; ///< DO 编号 + bool status = false; ///< 状态(true=高电平, false=低电平) + }; + /** + * @brief 音频播放参数 + */ + struct SoundArgs + { + std::string name; ///< 音频文件名 + int loop = 0; ///< 循环次数(0=播放一次, 1=循环播放) + int stop = 0; ///< 1=停止播放 + }; + /** + * @brief WaitDI 动作参数 + */ + struct WaitDIArgs + { + std::vector DI; ///< 需要等待的 DI 条件列表 + double timeout = 0.0; ///< 超时时间(秒),0 表示无限等待 + }; + /** + * @brief SetDO 动作参数 + */ + struct SetDOArgs + { + std::vector DO; ///< 要设置的 DO 列表 + }; + /** + * @brief PGV(二维码)二次定位参数 + */ + struct PgvParam + { + bool use_pgv = false; ///< 是否使用上视 PGV + bool use_down_pgv = false; ///< 是否使用下视 PGV + double pgv_adjust_dist = 0.0; ///< 最大调整半径(米) + double pgv_adjust_cx = 0.0; ///< 二维码坐标系下的 X 偏移(米) + double pgv_adjust_cy = 0.0; ///< 二维码坐标系下的 Y 偏移(米) + double pgv_x_adjust = 0.0; ///< 仅调整 X 方向误差(米) + }; + /** + * @brief 自由导航目标点(x, y, theta) + */ + struct FreeGoPoint + { + double x = 0.0; ///< X 坐标(米) + double y = 0.0; ///< Y 坐标(米) + double theta = 0.0; ///< 朝向角(弧度) + }; + /** + * @brief 脚本参数(通用) + */ + struct ScriptArgs + { + std::map str_kv; ///< 字符串键值对 + std::map num_kv; ///< 数字键值对 + std::vector do_list; ///< DO 列表(用于 setDO 等) + std::vector di_list; ///< DI 列表(用于 waitDI 等) + }; + + // ==================== 3051 单点导航 ==================== + /** + * @brief 3051 单点导航入参 + * + * 对应仙工 API 3051 (0x0BEB) 的请求数据区 + * + * @attention + * - **仅用于单车测试/验证,严禁用于多车调度场景**,否则会出现速度不连续、不跟随路径等危险。 + * - 下发此任务会取消当前正在执行的任务(不排队)。 + * - 成功下发后会自动清除指定的 warning/error 报错码。 + * - 支持自由导航(freeGo)和基于站点的路径导航两种模式。 + */ + struct RobotGoTargetReq + { + // -------- 必填字段 -------- + std::string source_id; ///< 起始站点 ID(若为 "SELF_POSITION" 表示从当前位置出发) + std::string id; ///< 目标站点 ID(若为 "SELF_POSITION" 表示原地执行 operation) + std::string task_id; ///< 任务 ID(非强制,但建议提供) + + // -------- 可选速度/角度控制 -------- + double angle = 0.0; ///< 目标点朝向角(弧度),仅对自由导航或目标站点有方向要求时使用 + std::string method; ///< 运动方式:"forward"(正走)或 "backward"(倒走) + double max_speed = 0.0; ///< 最大线速度(m/s),0 表示使用默认 + double max_wspeed = 0.0; ///< 最大角速度(rad/s) + double max_acc = 0.0; ///< 最大加速度(m/s²) + double max_wacc = 0.0; ///< 最大角加速度(rad/s²) + int64_t duration = 0; ///< 到达后等待时间(毫秒) + int32_t orientation = 0; ///< 全向车保持的角度(暂不使用) + bool spin = false; ///< 是否随动(用于托盘旋转) + int64_t delay = 0; ///< 延迟结束导航状态的时间(毫秒),缺省为 0 + int32_t start_rot_dir = 0; ///< 起步原地旋转方向:-1=顺时针, 0=就近, 1=逆时针 + int32_t end_rot_dir = 0; ///< 到点原地旋转方向(同上) + double reach_dist = 0.0; ///< 到点位置精度(米),默认由系统决定 + double reach_angle = 0.0; ///< 到点角度精度(弧度) + std::string skill_name; ///< 技能名称:"Action"(动作)或 "GotoSpecifiedPose"(路径导航) + + // -------- PGV 二次定位 -------- + PgvParam pgv; ///< PGV 参数 + + // -------- 设备操作(顶升/货叉/辊筒/牵引等) -------- + std::string operation; ///< 操作类型,如 "JackLoad", "ForkUnload", "RollerLoad", "HookLoad", "WaitDI", "SetDO", "sound", "Script" 等 + double jack_height = 0.0; ///< 顶升高度(米),用于 JackLoad/JackUnload/JackHeight + double start_height = 0.0; ///< 货叉起步前举升高度(米) + double end_height = 0.0; ///< 货叉到点后举升高度(米) + double fork_mid_height = 0.0; ///< 货叉行走过程中举升高度(米) + double fork_dist = 0.0; ///< 货叉前移距离(米),用于 ForkForward + std::string direction; ///< 方向:"left"/"right"/"front"/"back",用于辊筒或货叉 + bool recognize = false; ///< 是否启用视觉识别(用于货叉/顶升加载) + std::string recfile; ///< 识别文件路径(如 "shelf/s0002.shelf") + SoundArgs sounds_args; ///< 音频参数(当 operation="sound" 时) + WaitDIArgs wait_di_args; ///< WaitDI 参数 + SetDOArgs set_do_args; ///< SetDO 参数 + std::string script_name; ///< 脚本文件名(当 operation="Script" 时) + ScriptArgs script_args; ///< 脚本参数 + int32_t script_stage = 2; ///< 脚本执行阶段:0=导航前, 1=导航中, 2=导航后, 3=脚本控制导航 + + // -------- 原地旋转(特殊用法) -------- + double move_angle = 0.0; ///< 旋转角度(弧度),仅当 skill_name="GoByOdometer" 时使用 + double speed_w = 0.0; ///< 旋转角速度(rad/s) + int32_t loc_mode = 0; ///< 定位模式:1=激光定位, 0=里程定位 + + // -------- 自由导航(仅支持双轮差速底盘) -------- + FreeGoPoint freeGo; ///< 自由导航目标坐标(若提供此字段,则忽略 id 字段,使用坐标导航) + }; + + /** + * @brief 3051 单点导航出参 + */ + struct RobotGoTargetRes + { + int ret_code = 0; ///< 返回码,0 表示成功 + std::string create_on; ///< 数据创建时间戳 + std::string err_msg; ///< 错误信息 + }; + + //新增暂停导航响应结构体 + struct RobotTaskPauseRes { int ret_code = 0; std::string create_on; std::string err_msg; }; + // 继续导航响应结构体 + struct RobotTaskResumeRes { + int ret_code = 0; + std::string create_on; + std::string err_msg; + }; + // ==================== robot_control_stop 停止开环运动响应 ==================== + // 0x07D0 robot_control_stop 停止开环运动返回 + struct RobotControlStopRes + { + int ret_code = 0; + std::string create_on; + std::string err_msg; + }; + // 取消导航响应结构体 + struct RobotTaskCancelRes { + int ret_code = 0; + std::string create_on; + std::string err_msg; + }; + + // ==================== 类型别名(兼容旧代码) ==================== using AgvState = AgvStatusInfo; public: AbstractAgv() = default; ~AbstractAgv() override = default; + // 禁用拷贝,允许移动 AbstractAgv(const AbstractAgv&) = delete; AbstractAgv& operator=(const AbstractAgv&) = delete; AbstractAgv(AbstractAgv&&) = default; AbstractAgv& operator=(AbstractAgv&&) = default; - // 纯虚接口 + // ==================== 纯虚接口 ==================== + + /** + * @brief 获取当前设备运行状态 + * @return Status 枚举值 + */ virtual Status state() const = 0; + + /** + * @brief 获取最后一次发生的错误信息 + * @return 错误描述字符串 + */ virtual std::string lastError() const = 0; - + /** + * @brief 启动设备(建立连接、初始化资源等) + */ void start() override = 0; + + /** + * @brief 停止设备(断开连接、释放资源等) + */ void stop() override = 0; + + /** + * @brief 周期性更新设备状态(由上层循环调用,可处理心跳、重连等) + */ void update() override = 0; + /** + * @brief 获取 AGV 状态(封装了 getStatusInfo) + * @param state 输出参数,填充 AgvState 结构 + */ virtual void getState(AgvState& state) { state = AgvState{}; getStatusInfo(state); } + /** + * @brief 获取 AGV 基本信息(命令码 1000) + * @param info 输出参数,填充 AgvStatusInfo + */ virtual void getStatusInfo(AgvStatusInfo& info) = 0; - // 查询电池状态 + /** + * @brief 查询电池状态(命令码 1007) + * @param info 输出参数,填充 BatteryStatus + * @param simple 若为 true,只查询关键信息(可提高效率,具体取决于实现) + */ virtual void getBatteryStatus(BatteryStatus& info, bool simple = false) = 0; - // 查询机器人位置 + /** + * @brief 查询机器人当前位置(命令码 1004) + * @param info 输出参数,填充 RobotLocation + */ virtual void getRobotLocation(RobotLocation& info) = 0; - // 下载地图 + /** + * @brief 下载指定名称的地图(命令码 4011) + * @param info 输出参数,填充 DownloadMapResult(包含地图内容) + * @param map_name 要下载的地图名称 + */ virtual void downloadMap(DownloadMapResult& info, const std::string& map_name) = 0; - - // 查询地图状态 + /** + * @brief 查询地图状态(命令码 1300) + * @param info 输出参数,填充 MapStatus(当前地图、所有地图列表及文件信息) + */ virtual void getMapStatus(MapStatus& info) = 0; - // 上传地图 + /** + * @brief 上传地图(命令码 4010) + * @param info 输出参数,填充 UploadMapResult + * @param map_json 地图 JSON 字符串(完整的地图文件内容) + */ virtual void uploadMap(UploadMapResult& info, const std::string& map_json) = 0; /** - * @brief 抢占机器人控制权 - * @param info 出参,抢占结果 - * @param nick_name 入参,抢占者名称 + * @brief 抢占机器人控制权(命令码 4005) + * @param info 输出参数,填充 LockResult + * @param nick_name 抢占者昵称(用于标识调用方) */ virtual void lockRobotControl(LockResult& info, const std::string& nick_name) = 0; - - /** - * @brief 下发开环速度运动指令 命令码2010(0x07DA),使用控制端口sock_control_ - * @param res 出参 运动指令返回结果 - * @param req 入参 vx/vy/w/steer/real_steer/duration运动参数 - * @note 下发后会强制中断当前自动导航任务;多舵轮设备仅vx/vy/w生效 + * @brief 下发开环速度运动指令(命令码 2010) + * + * 使用控制端口(19205)。下发后会强制中断当前自动导航任务。 + * 对于多舵轮设备,仅 vx/vy/w 生效。 + * + * @param res 输出参数,运动指令返回结果 + * @param req 输入参数,包含 vx/vy/w/steer/real_steer/duration */ virtual void robotMotionControl(MotionCtrlRes& res, const MotionCtrlReq& req) = 0; /** - * @brief 切换载入地图 命令码2022(0x07E6),控制端口sock_control_ - * @param res 出参 执行结果 - * @param req 入参 目标地图名称 - */ + * @brief 切换载入地图(命令码 2022) + * + * 使用控制端口(19205)。目标地图必须已在机器人中存储。 + * + * @param res 输出参数,执行结果 + * @param req 输入参数,目标地图名称 + */ virtual void robotLoadMap(LoadMapRes& res, const LoadMapReq& req) = 0; - + /** + * @brief 查询当前导航状态(命令码 1020) + * + * 使用状态端口(19204)。 + * @param res 输出参数,导航全量状态 + * @param req 输入参数,simple=true 仅返回 task_status + */ + virtual void robotStatusTaskCurrent(RobotStatusTaskCurrentRes& res, const RobotStatusTaskCurrentReq& req) = 0; /** - * @brief 1020(0x03FC) robot_status_task_req 查询当前导航状态 状态端口 sock_status_ - * @param res 输出导航全量状态 - * @param req simple=true 仅返回task_status - */ - virtual void robotStatusTask(RobotStatusTaskRes& res, const RobotStatusTaskReq& req) = 0; - - - + * @brief 简单运动控制(设置速度) + * + * 默认实现抛出 logic_error,子类可重写。 + * @param vx X 方向速度 (m/s) + * @param vy Y 方向速度 (m/s) + * @param vw 角速度 (rad/s) + */ virtual void move(double vx, double vy, double vw) { throw std::logic_error("move not implemented"); } + /** + * @brief 停止运动 + * + * 默认实现抛出 logic_error,子类可重写。 + */ virtual void stopMove() { throw std::logic_error("stopMove not implemented"); } /** - * @brief 查询当前机器人控制权持有者 - * @param info 出参 控制权信息 + * @brief 查询当前机器人控制权持有者(命令码 1060) + * @param info 输出参数,填充 CurrentLockStatus */ virtual void getCurrentLockStatus(CurrentLockStatus& info) = 0; /** - * @brief 查询地图载入状态 命令码1022(0x03FE),状态端口sock_status_ - * @param res 出参 地图加载状态结果 - * @note loadmap_status=2载入中禁止重定位 + * @brief 查询地图载入状态(命令码 1022) + * + * 使用状态端口(19204)。 + * @param res 输出参数,包含 loadmap_status(0=失败, 1=成功, 2=载入中) + * @note 载入中(2)时禁止执行重定位操作 */ virtual void queryLoadMapStatus(QueryLoadMapStatusRes& res) = 0; - /** - * @brief 查询当前地图全部站点信息 命令码1301(0x0515),状态端口sock_status_ - * @param res 出参 站点数组与执行结果 + * @brief 查询当前地图全部站点信息(命令码 1301) + * + * 使用状态端口(19204)。 + * @param res 输出参数,包含站点数组与执行结果 */ virtual void queryStationList(QueryStationRes& res) = 0; - /** - * @brief 指定路径导航 命令码3066(0x0BFA),控制端口sock_control_ - * @param res 出参 下发任务结果 - * @param req 入参 多段站点导航序列 - * @note task_id、source_id、id 必填;任务串行追加执行 + * @brief 指定路径导航(命令码 3066) + * + * 使用导航端口(19206)。任务串行追加执行。 + * + * @param res 输出参数,下发任务结果(ret_code=0 仅表示接收成功,不代表执行完成) + * @param req 输入参数,多段站点导航序列(每个任务必须包含 task_id, source_id, id) + * @attention + * - source_id 和 id 之间必须有直接相连的线路,不可以跳点。 + * - 任务会排队执行,若前一个任务失败,后续任务自动取消。 + * - 适合多车调度场景,因为路径可预知、不会自主绕路。 */ virtual void robotGoTargetList(GoTargetListRes& res, const GoTargetListReq& req) = 0; + /** + * @brief 批量查询任务状态(命令码 1110) + * + * 使用状态端口(19204)。 + * @param res 输出参数,包含所有任务状态包(已完成+未完成) + * @param req 输入参数,要查询的 task_id 列表;若为空,则查询所有未完成+最近一条完成的任务 + */ + virtual void robotStatusTaskPackage(QueryTaskStatusPackageRes& res, const QueryTaskStatusPackageReq& req) = 0; + /** + * @brief 单点站点自动规划导航(命令码 3051) + * + * 使用导航端口(19206)。 + * @param res 输出参数,下发结果(ret_code=0 仅表示接收成功) + * @param req 完整导航参数(支持自由导航、固定路径导航、带设备操作等) + * @warning + * - **严禁用于多车调度场景**,仅限单车测试/任务链验证。 + * - 下发新任务会取消当前正在执行的任务(不排队)。 + * - 成功下发后会**自动清除**指定的 warning/error 报错码(详见协议文档)。 + * - 支持自由导航(freeGo),但仅限双轮差速底盘。 + */ + virtual void robotGoTarget(RobotGoTargetRes& res, const RobotGoTargetReq& req) = 0; + + + /** + * @brief 2000(0x07D0) robot_control_stop_req 停止开环速度运动指令 + * @param res 出参,执行结果 + */ + virtual void robotControlStop(RobotControlStopRes& res) = 0; + + + /** + * @brief 暂停当前导航任务(命令码 3001, 0x0BB9) + * @param res 输出参数,执行结果 + */ + virtual void robotTaskPause(RobotTaskPauseRes& res) = 0; + + + /** + * @brief 继续当前导航任务(命令码 3002, 0x0BBA) + * @param res 输出参数,执行结果 + */ + virtual void robotTaskResume(RobotTaskResumeRes& res) = 0; + + + /** + * @brief 取消当前导航任务(命令码 3003, 0x0BBB) + * @param res 输出参数,执行结果 + */ + virtual void robotTaskCancel(RobotTaskCancelRes& res) = 0; protected: - std::string id_; - std::string ip_; - // ======================== - // 多端口配置(对应 proto) - // ======================== - int port_status_{0}; // 19204 状态查询 - int port_control_{0}; // 19205 控制API - int port_nav_{0}; // 19206 导航API - int port_config_{0}; // 19207 配置API - int port_other_{0}; // 19210 其他API - int port_push_{0}; // 19301 推送 - bool enable_{false}; + // ==================== 保护成员变量 ==================== + std::string id_; ///< 设备 ID(唯一标识) + std::string ip_; ///< 设备 IP 地址 + + // 多端口配置(对应 proto 定义) + int port_status_{0}; ///< 状态查询端口(默认 19204) + int port_control_{0}; ///< 控制 API 端口(默认 19205) + int port_nav_{0}; ///< 导航 API 端口(默认 19206) + int port_config_{0}; ///< 配置 API 端口(默认 19207) + int port_other_{0}; ///< 其他 API 端口(默认 19210) + int port_push_{0}; ///< 推送端口(默认 19301) + + bool enable_{false}; ///< 设备启用标志 }; } // namespace cmvr::device diff --git a/cmvr-es/devices/agv/agv_src1100/include/agv_src1100.h b/cmvr-es/devices/agv/agv_src1100/include/agv_src1100.h index 847dcfed..13ab8d32 100644 --- a/cmvr-es/devices/agv/agv_src1100/include/agv_src1100.h +++ b/cmvr-es/devices/agv/agv_src1100/include/agv_src1100.h @@ -1,6 +1,11 @@ // // Created by linbo on 2025/6/20. // +/** + * @file agv_src1100.h + * @brief 仙工智能 SRC-1100/2200 系列 AGV 控制器设备实现类的头文件。 + * 继承自 AbstractAgv,实现了所有纯虚接口,并管理多端口 TCP 连接。 + */ #ifndef AGV_SRC1100_H #define AGV_SRC1100_H #pragma once @@ -10,78 +15,292 @@ #include #include #include "nlohmann/json.hpp" + using json = nlohmann::json; + namespace cmvr::device { - class AgvSrc1100 final : public AbstractAgv { - public: - explicit AgvSrc1100(const XmlNode& cfg); - explicit AgvSrc1100(const config::AGVsrc1100Config& cfg); - ~AgvSrc1100() override; +/** + * @brief 仙工智能 SRC-1100/2200 系列 AGV 控制器设备实现类 + * + * 继承自 AbstractAgv,实现了与仙工智能 SRC 系列控制器通信的具体协议。 + * 支持多端口(状态、控制、导航、配置、推送)并发连接和指令交互。 + * + * @note 该类不是线程安全的,外部调用需自行保证同一设备实例的串行访问。 + */ +class AgvSrc1100 final : public AbstractAgv { +public: + // ==================== 构造 / 析构 ==================== - Status state() const override; - std::string lastError() const override; + /** + * @brief 从 XML 配置节点构造设备实例(已弃用或未使用,保留兼容) + * @param cfg XML 配置节点,包含设备 ID、IP、端口、启用标志等 + */ + explicit AgvSrc1100(const XmlNode& cfg); - void start() override; - void stop() override; - void update() override; + /** + * @brief 从 Protobuf 配置对象构造设备实例 + * @param cfg AGVsrc1100Config 配置对象,包含设备 ID、IP、端口、启用标志等 + */ + explicit AgvSrc1100(const config::AGVsrc1100Config& cfg); - void getStatusInfo(AgvStatusInfo& info) override; - void getBatteryStatus(BatteryStatus& info, bool simple = false) override; - void getRobotLocation(RobotLocation& info) override; - void downloadMap(DownloadMapResult& info, const std::string& map_name) override; - void getMapStatus(MapStatus& info) override; - void uploadMap(UploadMapResult& info, const std::string& map_json) override; - void lockRobotControl(LockResult& info, const std::string& nick_name) override; - void getCurrentLockStatus(CurrentLockStatus& info) override; + /** + * @brief 析构函数,自动停止设备并释放资源 + */ + ~AgvSrc1100() override; - void robotMotionControl(MotionCtrlRes& res, const MotionCtrlReq& req) override; + // ==================== 生命周期管理(通用) ==================== - void robotLoadMap(LoadMapRes& res, const LoadMapReq& req) override; + /** + * @brief 获取当前设备运行状态 + * @return Status 枚举值(CREATED / INITIALIZED / RUNNING / PAUSED / STOPPED / FAULT) + */ + Status state() const override; - void queryLoadMapStatus(QueryLoadMapStatusRes& res) override; - void queryStationList(QueryStationRes& res) override; + /** + * @brief 获取最后一次发生的错误信息 + * @return 错误描述字符串,若无错误则返回空字符串 + */ + std::string lastError() const override; - void robotGoTargetList(GoTargetListRes& res, const GoTargetListReq& req) override; + /** + * @brief 启动设备,标记为运行状态 + * @note 所有 socket 连接应在构造时已完成,启动仅改变状态标志 + */ + void start() override; + + /** + * @brief 停止设备,关闭所有 TCP 连接并释放资源 + */ + void stop() override; + + /** + * @brief 周期性更新设备状态 + * @note 当前为空实现,可扩展心跳检测或重连逻辑 + */ + void update() override; + + // ==================== 端口 19204 – 机器人状态 API(允许 10 个连接) ==================== + // 功能:查询机器人各种状态信息(只读操作,不改变机器人状态) + + /** + * @brief 查询机器人基本信息(命令码 1000, robot_status_info_req) + * @param info 输出参数,填充 AgvStatusInfo + * @note 返回信息包括:版本、型号、地图名称、网络 IP、MAC、Wi-Fi 信号等 + */ + void getStatusInfo(AgvStatusInfo& info) override; + + /** + * @brief 查询电池状态(命令码 1007, robot_status_battery_req) + * @param info 输出参数,填充 BatteryStatus + * @param simple 若为 true,仅返回关键电量信息(当前实现未区分) + * @note 返回信息包括:电量百分比、温度、充放电状态、电压、电流等 + */ + void getBatteryStatus(BatteryStatus& info, bool simple = false) override; + + /** + * @brief 查询机器人当前位置(命令码 1004, robot_status_loc_req) + * @param info 输出参数,填充 RobotLocation + * @note 返回信息包括:世界坐标系 X/Y 坐标、朝向角、定位置信度、当前站点 + */ + void getRobotLocation(RobotLocation& info) override; + + /** + * @brief 查询地图状态(命令码 1300, robot_status_map_req) + * @param info 输出参数,填充 MapStatus + * @note 返回信息包括:当前载入的地图名称、所有存储的地图列表、文件详情 + */ + void getMapStatus(MapStatus& info) override; + + /** + * @brief 查询当前控制权持有者(命令码 1060, robot_status_current_lock_req) + * @param info 输出参数,填充 CurrentLockStatus + * @note 返回信息包括:是否被锁定、持有者 IP/端口/昵称、锁定时间等 + */ + void getCurrentLockStatus(CurrentLockStatus& info) override; + + /** + * @brief 查询地图载入状态(命令码 1022, robot_status_loadmap_req) + * @param res 输出参数,包含 loadmap_status + * @note loadmap_status: 0=失败, 1=成功, 2=载入中(载入中禁止重定位) + */ + void queryLoadMapStatus(QueryLoadMapStatusRes& res) override; + + /** + * @brief 查询当前地图站点列表(命令码 1301, robot_status_station_req) + * @param res 输出参数,填充 QueryStationRes,包含所有站点信息 + * @note 返回信息包括:站点 ID、类型、坐标、朝向角、属性等 + */ + void queryStationList(QueryStationRes& res) override; + + /** + * @brief 查询当前导航状态(命令码 1020, robot_status_task_req) + * @param res 输出参数,填充 RobotStatusTaskCurrentRes + * @param req 输入参数,simple=true 时只返回 task_status + * @note 返回信息包括:任务状态、任务类型、目标站点/坐标、已走/未走路径 + */ + void robotStatusTaskCurrent(RobotStatusTaskCurrentRes& res, const RobotStatusTaskCurrentReq& req) override; + + /** + * @brief 批量查询任务状态(命令码 1110, robot_status_task_status_package_req) + * @param res 输出参数,填充 QueryTaskStatusPackageRes + * @param req 输入参数,task_ids 列表;若为空则查询所有未完成 + 最近一条已完成的任务 + * @note 返回信息包括:任务列表、每个任务的 ID/状态/类型、进度百分比、剩余距离等 + */ + void robotStatusTaskPackage(QueryTaskStatusPackageRes& res, const QueryTaskStatusPackageReq& req) override; + + // ==================== 端口 19205 – 机器人控制 API(允许 5 个连接) ==================== + // 功能:下发控制指令,改变机器人运动或状态(非导航类指令) + + /** + * @brief 下发开环速度运动指令(命令码 2010, robot_control_motion_req) + * @param res 输出参数,执行结果 + * @param req 输入参数,包含 vx/vy/w/steer/real_steer/duration + * @warning 下发此指令会立即取消当前正在执行的导航任务 + * @note 多舵轮设备仅 vx/vy/w 生效,steer/real_steer 仅单舵轮设备有效 + */ + void robotMotionControl(MotionCtrlRes& res, const MotionCtrlReq& req) override; + + /** + * @brief 切换载入地图(命令码 2022, robot_control_loadmap_req) + * @param res 输出参数,执行结果 + * @param req 输入参数,目标地图名称 + * @note 目标地图必须已存在于机器人中,否则切换失败 + */ + void robotLoadMap(LoadMapRes& res, const LoadMapReq& req) override; + + // ==================== 端口 19206 – 机器人导航 API(允许 5 个连接) ==================== + // 功能:下发导航任务 + + /** + * @brief 指定路径导航(命令码 3066, robot_task_gotargetlist_req) + * @param res 输出参数,下发结果(ret_code=0 仅表示指令被接收,不表示执行完成) + * @param req 输入参数,包含 move_task_list(站点序列) + * @attention + * - 每个任务必须含 task_id、source_id、id 三个必填字段 + * - source_id 和 id 之间必须有直接相连的线路,不可跳点 + * - 任务会排队执行,前一个任务失败时后续任务自动取消 + * - 适合多车调度场景 + */ + void robotGoTargetList(GoTargetListRes& res, const GoTargetListReq& req) override; + + /** + * @brief 单点站点自动规划导航(命令码 3051, robot_task_gotarget_req) + * @param res 输出参数,下发结果(ret_code=0 仅表示指令被接收) + * @param req 输入参数,支持自由导航(freeGo)和基于站点的路径导航两种模式 + * @warning + * - **严禁用于多车调度场景**,仅限单车测试/任务链验证 + * - 下发新任务会取消当前正在执行的任务(不排队) + * - 成功下发后会自动清除指定的 warning/error 报错码 + * - 自由导航(freeGo)仅支持双轮差速底盘 + */ + void robotGoTarget(RobotGoTargetRes& res, const RobotGoTargetReq& req) override; + + // ==================== 端口 19207 – 机器人配置 API(允许 5 个连接) ==================== + // 功能:配置类操作(地图上传/下载、控制权管理、参数修改等) + + /** + * @brief 抢占机器人控制权(命令码 4005, robot_config_lock_req) + * @param info 输出参数,填充 LockResult + * @param nick_name 抢占者昵称,用于标识调用方 + * @note 抢占成功后调用方获得独占控制权,其他方仅可查询状态 + */ + void lockRobotControl(LockResult& info, const std::string& nick_name) override; + + /** + * @brief 上传地图(命令码 4010, robot_config_uploadmap_req) + * @param info 输出参数,填充 UploadMapResult + * @param map_json 地图 JSON 字符串(完整的地图文件内容) + * @note 地图数据较大时会自动分片发送,需确保 JSON 格式正确 + */ + void uploadMap(UploadMapResult& info, const std::string& map_json) override; + + /** + * @brief 下载指定地图(命令码 4011, robot_config_downloadmap_req) + * @param info 输出参数,填充 DownloadMapResult(包含地图 JSON 内容) + * @param map_name 要下载的地图名称 + * @note 下载的地图内容以 JSON 字符串形式存储在 info.map_content 中 + */ + void downloadMap(DownloadMapResult& info, const std::string& map_name) override; + + // ==================== 端口 19210 – 其他 API(允许 5 个连接) ==================== + // 功能:外设控制(顶升/货叉/辊筒/音频/IO 等) + // 当前未实现具体方法,预留扩展 + + // ==================== 端口 19301 – 机器人推送 API(允许 10 个连接) ==================== + // 功能:接收机器人主动推送的实时状态数据 + // 相关配置方法(如 9300)可能在此,当前未实现 - void robotStatusTask(RobotStatusTaskRes& res, const RobotStatusTaskReq& req) override; + // ---------- 新增任务控制接口(3001/3002/3003)及停止运动(2000) ---------- + void robotTaskPause(RobotTaskPauseRes& res) override; + void robotTaskResume(RobotTaskResumeRes& res) override; + void robotTaskCancel(RobotTaskCancelRes& res) override; + void robotControlStop(RobotControlStopRes& res) override; +private: + // ==================== 私有通信辅助函数 ==================== + + /** + * @brief 异步连接指定端口 + * @param sock 输出参数,连接成功后存储 socket 文件描述符 + * @param port 目标端口号(19204/19205/19206/19207/19210/19301) + * @note 在独立线程中执行,连接成功后将 sock 设置为有效值 + */ + void asyncConnect(int& sock, int port); + + /** + * @brief 发送请求并接收完整响应 + * @param sock 已连接的 socket 文件描述符 + * @param header 16 字节协议帧头 + * @return true 表示收发成功,current_json_ 中存储响应 JSON;false 表示失败 + * @note 内部先发送帧头,然后循环接收直到超时,最后提取 JSON 部分 + */ + bool sendAndRecv(int sock, const uint8_t* header); + + /** + * @brief 清空 socket 接收缓冲区中的残留数据 + * @param sock 目标 socket 文件描述符 + * @note 采用非阻塞方式读取并丢弃所有可读数据,防止粘包干扰 + */ + void flushSocket(int sock); + + /** + * @brief 设置 socket 接收超时时间 + * @param sock 目标 socket 文件描述符 + * @param timeout_ms 超时时间(毫秒),0 表示取消超时 + */ + void setSocketTimeout(int sock, int timeout_ms); + + /** + * @brief 解析 JSON 字符串并做异常捕获 + * @param json_str 输入的 JSON 字符串 + * @param root 输出参数,解析后的 json 对象 + * @param err_msg 输出参数,解析失败时的错误信息 + * @return true 表示解析成功,false 表示解析失败 + */ + bool parseJson(const std::string& json_str, json& root, std::string& err_msg); + // ==================== 成员变量 ==================== + mutable std::mutex mutex_; ///< 保护状态和错误信息的互斥锁 + std::atomic status_{Status::CREATED}; ///< 当前设备运行状态(原子变量) + std::string last_error_; ///< 最后一次错误信息 + std::string current_json_; ///< 最近一次响应的 JSON 内容(由 sendAndRecv 填充) + // ---------- 各端口 Socket 文件描述符 ---------- + int sock_status_ = -1; ///< 19204 – 状态 API 端口 + int sock_control_ = -1; ///< 19205 – 控制 API 端口 + int sock_nav_ = -1; ///< 19206 – 导航 API 端口 + int sock_config_ = -1; ///< 19207 – 配置 API 端口 + int sock_other_ = -1; ///< 19210 – 其他 API 端口 + int sock_push_ = -1; ///< 19301 – 推送 API 端口 - private: - void asyncConnect(int& sock, int port); - bool sendAndRecv(int sock, const uint8_t* header); - void flushSocket(int sock); - void setSocketTimeout(int sock, int timeout_ms); - - - - // 辅助函数:将JSON字符串解析为Json::Value,同时做错误处理 - bool parseJson(const std::string& json_str, json& root, std::string& err_msg); - - mutable std::mutex mutex_; - std::atomic status_{Status::CREATED}; - std::string last_error_; - std::string current_json_; - - - - // 多socket - int sock_status_ = -1; - int sock_control_ = -1; - int sock_nav_ = -1; - int sock_config_ = -1; - int sock_other_ = -1; - int sock_push_ = -1; - - std::vector connect_threads_; - mutable std::mutex connect_mutex_; - }; + std::vector connect_threads_; ///< 各端口异步连接线程 + mutable std::mutex connect_mutex_; ///< 保护 connect_threads_ 的互斥锁 +}; } // namespace cmvr::device diff --git a/cmvr-es/devices/agv/agv_src1100/src/agv_src1100.cpp b/cmvr-es/devices/agv/agv_src1100/src/agv_src1100.cpp index f657eb1a..558e621b 100644 --- a/cmvr-es/devices/agv/agv_src1100/src/agv_src1100.cpp +++ b/cmvr-es/devices/agv/agv_src1100/src/agv_src1100.cpp @@ -1,3 +1,15 @@ +// ============================================================================ +// 文件名: agv_src1100.cpp +// 描述: 仙工智能 SRC-1100/2200 系列 AGV 控制器设备实现 +// 端口分类: +// 19204 - 机器人状态 API(查询位置/电池/任务/地图/控制权等) +// 19205 - 机器人控制 API(开环运动/切换地图等) +// 19206 - 机器人导航 API(路径导航/指定路径导航等) +// 19207 - 机器人配置 API(地图上传下载/控制权抢占等) +// 19210 - 其他 API(外设控制/音频/IO 等)【未实现】 +// 19301 - 机器人推送 API(主动状态推送)【未实现】 +// ============================================================================ + #include "../include/agv_src1100.h" #include #include @@ -10,1821 +22,2750 @@ // #include "json/json.h" #include "nlohmann/json.hpp" using json = nlohmann::json; -namespace cmvr::device { +namespace cmvr::device +{ + // ============================================================================ + // 构造函数 / 析构函数 + // ============================================================================ -// ====================================================================== -// 构造函数(多端口版本) -// ====================================================================== + /** + * @brief 从 Protobuf 配置对象构造设备实例 + * @param cfg AGVsrc1100Config 配置对象 + * @note 连接所有端口:19204/19205/19206/19207/19210/19301 + */ + AgvSrc1100::AgvSrc1100(const config::AGVsrc1100Config& cfg) : AbstractAgv() { + // 打印构造日志,便于调试追踪 + LOG(INFO) << "[AgvSrc1100 Create] this_addr=" << this + << " cfg_id=" << cfg.id() + << " cfg_ip=" << cfg.ip() + << " cfg_port_config=" << cfg.port_config(); + // 从配置中读取设备标识信息 + id_ = cfg.id(); // 设备 ID + ip_ = cfg.ip(); // 设备 IP 地址 + enable_ = cfg.enable(); // 是否启用 -AgvSrc1100::AgvSrc1100(const config::AGVsrc1100Config& cfg) : AbstractAgv() { - LOG(INFO) << "[AgvSrc1100 Create] this_addr=" << this - << " cfg_id=" << cfg.id() - << " cfg_ip=" << cfg.ip() - << " cfg_port_config=" << cfg.port_config(); - id_ = cfg.id(); - ip_ = cfg.ip(); - enable_ = cfg.enable(); + // 初始化各端口号(对应仙工协议的不同服务端口) + port_status_ = cfg.port_status(); // 19204 – 状态查询 + port_control_ = cfg.port_control(); // 19205 – 控制指令 + port_nav_ = cfg.port_nav(); // 19206 – 导航任务 + port_config_ = cfg.port_config(); // 19207 – 配置操作 + port_other_ = cfg.port_other(); // 19210 – 其他外设 + port_push_ = cfg.port_push(); // 19301 – 数据推送 - port_status_ = cfg.port_status(); - port_control_ = cfg.port_control(); - port_nav_ = cfg.port_nav(); - port_config_ = cfg.port_config(); - port_other_ = cfg.port_other(); - port_push_ = cfg.port_push(); + // 如果设备启用,则异步连接所有端口 + if (enable_) { + connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_status_), port_status_); + connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_control_), port_control_); + connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_nav_), port_nav_); + connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_config_), port_config_); + connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_other_), port_other_); + connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_push_), port_push_); + } - if (enable_) { - connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_status_), port_status_); - connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_control_), port_control_); - connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_nav_), port_nav_); - connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_config_), port_config_); - connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_other_), port_other_); - connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_push_), port_push_); + LOG(INFO) << "port_status=" << port_status_ + << ", port_control=" << port_control_ + << ", port_nav=" << port_nav_ + << ", port_config=" << port_config_ + << ", port_other=" << port_other_ + << ", port_push=" << port_push_; } -} -AgvSrc1100::~AgvSrc1100() { - stop(); - for (auto& t : connect_threads_) { - if (t.joinable()) t.join(); + /** + * @brief 析构函数:停止设备并等待所有连接线程结束 + */ + AgvSrc1100::~AgvSrc1100() { + stop(); // 关闭所有 socket + // 等待所有异步连接线程结束 + for (auto& t : connect_threads_) { + if (t.joinable()) t.join(); + } } -} -AbstractAgv::Status AgvSrc1100::state() const { - return status_; -} + // ============================================================================ + // 生命周期管理(通用接口) + // ============================================================================ -std::string AgvSrc1100::lastError() const { - std::lock_guard lock(mutex_); - return last_error_; -} + /** + * @brief 获取当前设备运行状态 + * @return Status 枚举值 + */ + AbstractAgv::Status AgvSrc1100::state() const { + return status_; + } -// ====================================================================== -// 通用异步连接 -// ====================================================================== -void AgvSrc1100::asyncConnect(int& sock, int port) { - int s = socket(AF_INET, SOCK_STREAM, 0); - if (s < 0) { + /** + * @brief 获取最后一次发生的错误信息 + * @return 错误描述字符串 + */ + std::string AgvSrc1100::lastError() const { std::lock_guard lock(mutex_); - last_error_ = "socket create failed"; - status_ = Status::FAULT; - return; + return last_error_; } - struct sockaddr_in addr{}; - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - inet_pton(AF_INET, ip_.c_str(), &addr.sin_addr); - - if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) { - close(s); - std::lock_guard lock(mutex_); - last_error_ = "connect failed port:" + std::to_string(port); - status_ = Status::FAULT; - return; + /** + * @brief 启动设备:标记为运行状态 + * @note socket 连接已在构造时完成,此处仅改变状态标志 + */ + void AgvSrc1100::start() { + status_ = Status::RUNNING; } - { - std::lock_guard lock(mutex_); - sock = s; - status_ = Status::INITIALIZED; + /** + * @brief 停止设备:关闭所有 TCP 连接并释放资源 + */ + void AgvSrc1100::stop() { + // 定义 lambda 关闭单个 socket 并置为 -1 + auto closeSocket = [](int& s) { + if (s >= 0) { close(s); s = -1; } + }; + // 关闭所有端口的 socket + closeSocket(sock_status_); + closeSocket(sock_control_); + closeSocket(sock_nav_); + closeSocket(sock_config_); + closeSocket(sock_other_); + closeSocket(sock_push_); + status_ = Status::STOPPED; } -} -void AgvSrc1100::start() { - status_ = Status::RUNNING; -} + /** + * @brief 周期性更新设备状态 + * @note 当前为空实现,预留扩展(如心跳检测、自动重连等) + */ + void AgvSrc1100::update() {} -void AgvSrc1100::stop() { - auto closeSocket = [](int& s) { - if (s >= 0) { close(s); s = -1; } - }; - closeSocket(sock_status_); - closeSocket(sock_control_); - closeSocket(sock_nav_); - closeSocket(sock_config_); - closeSocket(sock_other_); - closeSocket(sock_push_); - status_ = Status::STOPPED; -} + // ============================================================================ + // 私有通信辅助函数 + // ============================================================================ -void AgvSrc1100::update() {} + /** + * @brief 清空 socket 接收缓冲区残留数据,防止粘包 + * @param sock 目标 socket + * @note 非阻塞读取并丢弃所有可读数据 + */ + void AgvSrc1100::flushSocket(int sock) { + uint8_t tmp[128]; // 临时缓冲区 + ssize_t rlen; + // MSG_DONTWAIT: 非阻塞模式,读到没有数据时立即返回 + while ((rlen = recv(sock, tmp, sizeof(tmp), MSG_DONTWAIT)) > 0); + } -void AgvSrc1100::flushSocket(int sock) { - uint8_t tmp[128]; - ssize_t rlen; - while ((rlen = recv(sock, tmp, sizeof(tmp), MSG_DONTWAIT)) > 0); -} + /** + * @brief 设置 socket 接收超时时间 + * @param sock 目标 socket + * @param timeout_ms 超时时间(毫秒),0 表示取消超时 + */ + void AgvSrc1100::setSocketTimeout(int sock, int timeout_ms) { + struct timeval tv; + tv.tv_sec = timeout_ms / 1000; // 秒 + tv.tv_usec = (timeout_ms % 1000) * 1000; // 微秒 + // SO_RCVTIMEO: 设置接收超时 + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + } -void AgvSrc1100::setSocketTimeout(int sock, int timeout_ms) { - struct timeval tv; - tv.tv_sec = timeout_ms / 1000; - tv.tv_usec = (timeout_ms % 1000) * 1000; - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); -} + /** + * @brief 异步连接指定端口 + * @param sock 输出参数,连接成功后的 socket 描述符 + * @param port 目标端口号 + * @note 在独立线程中执行,连接失败时设置错误状态 + */ + void AgvSrc1100::asyncConnect(int& sock, int port) { + // 创建 TCP socket + int s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) { + std::lock_guard lock(mutex_); + last_error_ = "socket create failed"; + status_ = Status::FAULT; + return; + } -// ====================================================================== -// 增强版收发:确保完整接收所有数据帧 -// ====================================================================== + // 配置目标地址 + struct sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, ip_.c_str(), &addr.sin_addr); // IP 地址转换 + + // 发起 TCP 连接 + if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + close(s); // 连接失败,关闭 socket + std::lock_guard lock(mutex_); + last_error_ = "connect failed port:" + std::to_string(port); + status_ = Status::FAULT; + return; + } + + // 连接成功,保存 socket 描述符 + { + std::lock_guard lock(mutex_); + sock = s; + status_ = Status::INITIALIZED; + } + } + + /** + * @brief 发送请求并接收完整响应 + * @param sock 已连接的 socket + * @param header 16 字节协议帧头(包含命令码等) + * @return true 表示成功,current_json_ 存储响应 JSON + * @note 适用于无负载或简单查询命令(如 1000/1004/1007) + */ bool AgvSrc1100::sendAndRecv(int sock, const uint8_t* header) { - std::lock_guard lock(mutex_); + // 加锁,防止多个线程同时操作 current_json_ + std::lock_guard lock(mutex_); - if (sock < 0) { - last_error_ = "socket not connected"; - return false; - } - - flushSocket(sock); - - // 发送16字节请求包头 - if (send(sock, header, 16, MSG_NOSIGNAL) != 16) { - last_error_ = "send header failed"; - return false; - } - - // 整体接收超时 1000ms - setSocketTimeout(sock, 100); - - const int RECV_BUF_LEN = 16384; - uint8_t recvBuf[RECV_BUF_LEN] = {0}; - int totalRecv = 0; - - // 循环接收,直到读空 or 缓冲区满 - while (totalRecv < RECV_BUF_LEN) - { - int ret = recv(sock, recvBuf + totalRecv, RECV_BUF_LEN - totalRecv, 0); - if (ret <= 0) break; // 读空/超时,退出 - - totalRecv += ret; - } - - setSocketTimeout(sock, 0); - - if (totalRecv <= 0) { - last_error_ = "no data received"; - return false; - } - - std::string rawData(reinterpret_cast(recvBuf), totalRecv); - size_t jsonStart = rawData.find('{'); - size_t jsonEnd = rawData.rfind('}'); - - // 兜底:如果没找到结尾 },手动补全,修复截断报错 - if (jsonStart == std::string::npos) - { - last_error_ = "can not find '{'"; - return false; - } - if (jsonEnd == std::string::npos) - { - // 找不到 },截取到末尾并补 } - current_json_ = rawData.substr(jsonStart) + "}"; - } - else - { - current_json_ = rawData.substr(jsonStart, jsonEnd - jsonStart + 1); - } - - - fflush(stdout); - - last_error_ = "ok"; - return true; -} - -// ====================================================================== -// 辅助JSON解析函数 -// ====================================================================== - bool AgvSrc1100::parseJson(const std::string& json_str, json& root, std::string& err_msg) -{ - root.clear(); - try - { - root = json::parse(json_str); - } - catch (json::parse_error& e) - { - err_msg = "JSON parse error: " + std::string(e.what()); - return false; - } - return true; -} - -// =========================================================================== -// 接口1:查询设备信息 → 19204 命令码 1000 (0x03E8) -// =========================================================================== - void AgvSrc1100::getStatusInfo(AgvStatusInfo& info) { - int sock = sock_status_; - if (sock < 0) { - info.ret_code = -1; - info.err_msg = "socket not connected"; - return; - } - - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, - 0x03, 0xE8, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - }; - - if (!sendAndRecv(sock, send_buf)) { - info.ret_code = -2; - info.err_msg = last_error_; - return; - } - - std::string json_str; - { - std::lock_guard lk(mutex_); - json_str = current_json_; - } - - // 兜底修复JSON闭合 - if (!json_str.empty()) - { - if (json_str.front() != '{') json_str = "{" + json_str; - if (json_str.back() != '}') json_str += "}"; - } - - if (json_str.empty()) { - info.ret_code = -4; - info.err_msg = "empty json data"; - return; - } - - json root; - std::string err; - if (!parseJson(json_str, root, err)) { - info.ret_code = -3; - info.err_msg = err; - return; - } - - info.id = root.value("id", ""); - info.vehicle_id = root.value("vehicle_id", ""); - info.version = root.value("version", ""); - info.model = root.value("model", ""); - info.dsp_version = root.value("dsp_version", ""); - info.current_ip = root.value("current_ip", ""); - info.mac = root.value("MAC", ""); - info.rssi = root.value("rssi", 0); - info.ret_code = root.value("ret_code", 0); - info.err_msg = root.value("err_msg", ""); -} - - -// =========================================================================== -// 接口:查询机器人电池状态 → 命令码 1007 (0x03EF) -// =========================================================================== -void AgvSrc1100::getBatteryStatus(BatteryStatus& info, bool simple) -{ - int sock = sock_status_; - if (sock < 0) - { - info.ret_code = -1; - info.err_msg = "socket not connected"; - return; - } - - // 步骤1:构造16字节请求帧 - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, - 0x03, 0xEF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - }; - - if (!sendAndRecv(sock, send_buf)) - { - info.ret_code = -2; - info.err_msg = last_error_; - return; - } - - // 步骤2:数据拷贝 + JSON 兜底 + 解析 - std::string json_str; - { - std::lock_guard lk(mutex_); - json_str = current_json_; - } - - if (!json_str.empty()) - { - if (json_str.front() != '{') json_str = "{" + json_str; - if (json_str.back() != '}') json_str += "}"; - } - - if (json_str.empty()) - { - info.ret_code = -4; - info.err_msg = "empty json data"; - return; - } - - json root; - std::string err; - if (!parseJson(json_str, root, err)) - { - info.ret_code = -3; - info.err_msg = err; - return; - } - - // 步骤3:字段赋值 - info.battery_level = root.value("battery_level", 0.0); - info.battery_temp = root.value("battery_temp", 0.0); - info.charging = root.value("charging", false); - info.voltage = root.value("voltage", 0.0); - info.current = root.value("current", 0.0); - - info.max_charge_voltage = root.value("max_charge_voltage", -1.0); - info.max_charge_current = root.value("max_charge_current", -1.0); - - info.manual_charge = root.value("manual_charge", false); - info.auto_charge = root.value("auto_charge", false); - info.battery_cycle = root.value("battery_cycle", 0); - - info.battery_user_data = root.value("battery_user_data", ""); - info.extra = root.value("extra", ""); - - info.ret_code = root.value("ret_code", 0); - info.create_on = root.value("create_on", ""); - info.err_msg = root.value("err_msg", ""); -} - - - // =========================================================================== - // 接口:查询机器人位置 → 命令码 1004 (0x03EC) - // =========================================================================== - void AgvSrc1100::getRobotLocation(RobotLocation& info) -{ - int sock = sock_status_; - if (sock < 0) - { - info.ret_code = -1; - info.err_msg = "socket not connected"; - return; - } - - // 步骤1:构造16字节请求帧 - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, - 0x03, 0xEC, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - }; - - if (!sendAndRecv(sock, send_buf)) - { - info.ret_code = -2; - info.err_msg = last_error_; - return; - } - - // 步骤2:数据拷贝 + JSON兜底 + 解析 - std::string json_str; - { - std::lock_guard lk(mutex_); - json_str = current_json_; - } - - if (!json_str.empty()) - { - if (json_str.front() != '{') json_str = "{" + json_str; - if (json_str.back() != '}') json_str += "}"; - } - - if (json_str.empty()) - { - info.ret_code = -4; - info.err_msg = "empty json data"; - return; - } - - json root; - std::string err; - if (!parseJson(json_str, root, err)) - { - info.ret_code = -3; - info.err_msg = err; - return; - } - - // 步骤3:字段赋值 - info.x = root.value("x", 0.0); - info.y = root.value("y", 0.0); - info.angle = root.value("angle", 0.0); - info.confidence = root.value("confidence", 0.0); - info.current_station = root.value("current_station", ""); - info.last_station = root.value("last_station", ""); - info.loc_method = root.value("loc_method", 0); - info.ret_code = root.value("ret_code", 0); - info.create_on = root.value("create_on", ""); - info.err_msg = root.value("err_msg", ""); -} - - - // =========================================================================== -// 接口:下载地图 命令码 4011(0x0FAB) -// =========================================================================== -void AgvSrc1100::downloadMap(DownloadMapResult& info, const std::string& map_name) -{ - int sock = sock_config_; - if (sock < 0) - { - info.ret_code = -1; - info.err_msg = "socket not connected"; - return; - } - - // 组装请求JSON - std::string req_json = R"({"map_name":")" + map_name + R"("})"; - uint8_t payload_len = static_cast(req_json.size()); - - // 严格对齐协议帧头 - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, payload_len, - 0x0F, 0xAB, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - // 发送帧头 - if (send(sock, send_buf, 16, MSG_NOSIGNAL) != 16) - { - info.ret_code = -2; - info.err_msg = "send frame header failed"; - return; - } - // 发送JSON负载 - if (send(sock, req_json.data(), req_json.size(), MSG_NOSIGNAL) != (ssize_t)req_json.size()) - { - info.ret_code = -2; - info.err_msg = "send payload failed"; - return; - } - - // 增大缓冲区 + 循环接收,确保大数据收完整 - const int RECV_BUF_LEN = 65536; - std::vector recvBuf(RECV_BUF_LEN); - std::string rawData; - int ret = 0; - - setSocketTimeout(sock, 500); // 延长超时适配大地图 - while ((ret = recv(sock, recvBuf.data(), RECV_BUF_LEN, 0)) > 0) - { - rawData.append(reinterpret_cast(recvBuf.data()), ret); - } - setSocketTimeout(sock, 0); - - if (rawData.empty()) - { - info.ret_code = -2; - info.err_msg = "no response data"; - return; - } - - // 截取JSON区间 - size_t jsonStart = rawData.find('{'); - size_t jsonEnd = rawData.rfind('}'); - std::string json_str; - - if (jsonStart == std::string::npos) - { - info.ret_code = -4; - info.err_msg = "json not found"; - return; - } - if (jsonEnd == std::string::npos) - { - // 兜底补全闭合符 - json_str = rawData.substr(jsonStart) + "}"; - } - else - { - json_str = rawData.substr(jsonStart, jsonEnd - jsonStart + 1); - } - - // JSON解析 - json root; - std::string err; - if (!parseJson(json_str, root, err)) - { - info.ret_code = -3; - info.err_msg = err; - return; - } - - // 字段赋值 - info.map_name = map_name; - info.ret_code = root.value("ret_code", 0); - info.create_on = root.value("create_on", ""); - info.err_msg = root.value("err_msg", ""); - info.map_content = (0 == info.ret_code) ? json_str : ""; -} - - - - // =========================================================================== -// 接口:查询地图状态 → 命令码 1300 (0x0514) -// =========================================================================== -void AgvSrc1100::getMapStatus(MapStatus& info) -{ - int sock = sock_status_; - if (sock < 0) - { - info.ret_code = -1; - info.err_msg = "socket not connected"; - return; - } - - // 步骤1:构造16字节请求帧 - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, - 0x05, 0x14, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - }; - - if (!sendAndRecv(sock, send_buf)) - { - info.ret_code = -2; - info.err_msg = last_error_; - return; - } - - // 步骤2:数据拷贝 + JSON兜底 + 解析 - std::string json_str; - { - std::lock_guard lk(mutex_); - json_str = current_json_; - } - - if (!json_str.empty()) - { - if (json_str.front() != '{') json_str = "{" + json_str; - if (json_str.back() != '}') json_str += "}"; - } - - if (json_str.empty()) - { - info.ret_code = -4; - info.err_msg = "empty json data"; - return; - } - - json root; - std::string err; - if (!parseJson(json_str, root, err)) - { - info.ret_code = -3; - info.err_msg = err; - return; - } - - // 步骤3:字段赋值 - info.current_map = root.value("current_map", ""); - info.current_map_md5 = root.value("current_map_md5", ""); - info.ret_code = root.value("ret_code", 0); - info.create_on = root.value("create_on", ""); - info.err_msg = root.value("err_msg", ""); - - // 解析地图名数组 - if (root.contains("maps") && root["maps"].is_array()) - { - for (auto& item : root["maps"]) - { - info.maps.push_back(item.get()); + if (sock < 0) { + last_error_ = "socket not connected"; + return false; } - } - // 解析地图文件详情数组 - if (root.contains("map_files_info") && root["map_files_info"].is_array()) - { - for (auto& item : root["map_files_info"]) - { - MapFileInfo fileInfo; - fileInfo.name = item.value("name", ""); - fileInfo.modified = item.value("modified", ""); - fileInfo.size = item.value("size", 0LL); - info.map_files_info.push_back(fileInfo); + // 清空残留数据,防止读取到上一次的响应 + flushSocket(sock); + + // 发送 16 字节协议帧头 + if (send(sock, header, 16, MSG_NOSIGNAL) != 16) { + last_error_ = "send header failed"; + return false; } - } -} + // 设置接收超时 100ms,避免无限阻塞 + setSocketTimeout(sock, 100); -// =========================================================================== -// 接口:上传地图 → 命令码 4010 (0x0FAA) -// =========================================================================== -void AgvSrc1100::uploadMap(UploadMapResult& info, const std::string& map_json) -{ - int sock = sock_config_; - if (sock < 0) - { - info.ret_code = -1; - info.err_msg = "socket not connected"; - return; - } + const int RECV_BUF_LEN = 16384; + uint8_t recvBuf[RECV_BUF_LEN] = {0}; + int totalRecv = 0; - // 清空socket残留数据 - flushSocket(sock); - // 延长IO超时 1s - setSocketTimeout(sock, 1000); - - uint16_t payload_len = static_cast(map_json.size()); - // 帧头:第7、8字节 = 负载长度(大端),修正单字节溢出问题 - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x00, - 0x00, 0x00, - static_cast((payload_len >> 8) & 0xFF), // 高字节 - static_cast(payload_len & 0xFF), // 低字节 - 0x0F, 0xAA, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - // 发送16字节帧头,循环重试 - int totalSent = 0; - int retries = 3; - while (totalSent < 16 && retries-- > 0) - { - int ret = send(sock, send_buf + totalSent, 16 - totalSent, 0); - if (ret <= 0) + // 循环接收数据,直到读空或缓冲区满 + while (totalRecv < RECV_BUF_LEN) { - usleep(10000); - continue; + int ret = recv(sock, recvBuf + totalRecv, RECV_BUF_LEN - totalRecv, 0); + if (ret <= 0) break; // 读空或超时,退出循环 + totalRecv += ret; } - totalSent += ret; - } - if (totalSent != 16) - { + + // 取消超时设置 setSocketTimeout(sock, 0); - info.ret_code = -2; - info.err_msg = "send frame header failed"; - return; + + if (totalRecv <= 0) { + last_error_ = "no data received"; + return false; + } + + // 将接收到的数据转为字符串 + std::string rawData(reinterpret_cast(recvBuf), totalRecv); + // 查找 JSON 起始和结束位置 + size_t jsonStart = rawData.find('{'); + size_t jsonEnd = rawData.rfind('}'); + + // 提取 JSON 内容,兜底处理不完整数据 + if (jsonStart == std::string::npos) + { + last_error_ = "can not find '{'"; + return false; + } + if (jsonEnd == std::string::npos) + { + // 找不到 },截取到末尾并补上 } + current_json_ = rawData.substr(jsonStart) + "}"; + } + else + { + current_json_ = rawData.substr(jsonStart, jsonEnd - jsonStart + 1); + } + + fflush(stdout); + last_error_ = "ok"; + return true; } - // 分片发送大JSON数据,4096字节一片 - const int SLICE_SIZE = 4096; - size_t offset = 0; - size_t totalSize = map_json.size(); - const char* data = map_json.c_str(); - - while (offset < totalSize) + /** + * @brief 解析 JSON 字符串并做异常捕获 + * @param json_str 输入的 JSON 字符串 + * @param root 输出参数,解析后的 json 对象 + * @param err_msg 输出参数,解析失败时的错误信息 + * @return true 表示解析成功,false 表示失败 + */ + bool AgvSrc1100::parseJson(const std::string& json_str, json& root, std::string& err_msg) { - int sendLen = std::min((size_t)SLICE_SIZE, totalSize - offset); - int ret = send(sock, data + offset, sendLen, 0); - if (ret <= 0) + root.clear(); + try + { + root = json::parse(json_str); // 使用 nlohmann/json 解析 + } + catch (json::parse_error& e) // 捕获解析异常 + { + err_msg = "JSON parse error: " + std::string(e.what()); + return false; + } + return true; + } + + // ============================================================================ + // 端口 19204 – 机器人状态 API(允许 10 个连接) + // 功能:查询机器人各种状态信息(只读操作) + // ============================================================================ + + // -------------------------------------------------------------------------- + // 1000 - robot_status_info_req 查询机器人基本信息 + // -------------------------------------------------------------------------- + void AgvSrc1100::getStatusInfo(AgvStatusInfo& info) { + int sock = sock_status_; + if (sock < 0) { + info.ret_code = -1; + info.err_msg = "socket not connected"; + return; + } + + // 构造 16 字节协议帧头 + // 格式: 0x5A 0x01 0x00 0x01 + 长度(4字节) + 命令码(2字节) + 保留(6字节) + // 命令码 0x03E8 = 1000 + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x03, 0xE8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + // 发送请求并接收响应 + if (!sendAndRecv(sock, send_buf)) { + info.ret_code = -2; + info.err_msg = last_error_; + return; + } + + // 从成员变量中读取响应 JSON + std::string json_str; + { + std::lock_guard lk(mutex_); + json_str = current_json_; + } + + // 兜底修复 JSON 格式(补全花括号) + if (!json_str.empty()) + { + if (json_str.front() != '{') json_str = "{" + json_str; + if (json_str.back() != '}') json_str += "}"; + } + + if (json_str.empty()) { + info.ret_code = -4; + info.err_msg = "empty json data"; + return; + } + + // 解析 JSON + json root; + std::string err; + if (!parseJson(json_str, root, err)) { + info.ret_code = -3; + info.err_msg = err; + return; + } + + // 提取字段,不存在的字段使用默认值 + info.id = root.value("id", ""); + info.vehicle_id = root.value("vehicle_id", ""); + info.version = root.value("version", ""); + info.model = root.value("model", ""); + info.dsp_version = root.value("dsp_version", ""); + info.current_ip = root.value("current_ip", ""); + info.mac = root.value("MAC", ""); + info.rssi = root.value("rssi", 0); + info.ret_code = root.value("ret_code", 0); + info.err_msg = root.value("err_msg", ""); + } + + + // -------------------------------------------------------------------------- + // 1007 - robot_status_battery_req 查询电池状态 + // -------------------------------------------------------------------------- + void AgvSrc1100::getBatteryStatus(BatteryStatus& info, bool simple) + { + int sock = sock_status_; + if (sock < 0) + { + info.ret_code = -1; + info.err_msg = "socket not connected"; + return; + } + + // 命令码 0x03EF = 1007 + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x03, 0xEF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + if (!sendAndRecv(sock, send_buf)) + { + info.ret_code = -2; + info.err_msg = last_error_; + return; + } + + std::string json_str; + { + std::lock_guard lk(mutex_); + json_str = current_json_; + } + + if (!json_str.empty()) + { + if (json_str.front() != '{') json_str = "{" + json_str; + if (json_str.back() != '}') json_str += "}"; + } + + if (json_str.empty()) + { + info.ret_code = -4; + info.err_msg = "empty json data"; + return; + } + + json root; + std::string err; + if (!parseJson(json_str, root, err)) + { + info.ret_code = -3; + info.err_msg = err; + return; + } + + // 提取电池相关字段 + info.battery_level = root.value("battery_level", 0.0); + info.battery_temp = root.value("battery_temp", 0.0); + info.charging = root.value("charging", false); + info.voltage = root.value("voltage", 0.0); + info.current = root.value("current", 0.0); + info.max_charge_voltage = root.value("max_charge_voltage", -1.0); + info.max_charge_current = root.value("max_charge_current", -1.0); + info.manual_charge = root.value("manual_charge", false); + info.auto_charge = root.value("auto_charge", false); + info.battery_cycle = root.value("battery_cycle", 0); + info.battery_user_data = root.value("battery_user_data", ""); + info.extra = root.value("extra", ""); + info.ret_code = root.value("ret_code", 0); + info.create_on = root.value("create_on", ""); + info.err_msg = root.value("err_msg", ""); + } + + + // -------------------------------------------------------------------------- + // 1004 - robot_status_loc_req 查询机器人位置 + // -------------------------------------------------------------------------- + void AgvSrc1100::getRobotLocation(RobotLocation& info) + { + int sock = sock_status_; + if (sock < 0) + { + info.ret_code = -1; + info.err_msg = "socket not connected"; + return; + } + + // 命令码 0x03EC = 1004 + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x03, 0xEC, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + if (!sendAndRecv(sock, send_buf)) + { + info.ret_code = -2; + info.err_msg = last_error_; + return; + } + + std::string json_str; + { + std::lock_guard lk(mutex_); + json_str = current_json_; + } + + if (!json_str.empty()) + { + if (json_str.front() != '{') json_str = "{" + json_str; + if (json_str.back() != '}') json_str += "}"; + } + + if (json_str.empty()) + { + info.ret_code = -4; + info.err_msg = "empty json data"; + return; + } + + json root; + std::string err; + if (!parseJson(json_str, root, err)) + { + info.ret_code = -3; + info.err_msg = err; + return; + } + + // 提取位置相关字段 + info.x = root.value("x", 0.0); + info.y = root.value("y", 0.0); + info.angle = root.value("angle", 0.0); + info.confidence = root.value("confidence", 0.0); + info.current_station = root.value("current_station", ""); + info.last_station = root.value("last_station", ""); + info.loc_method = root.value("loc_method", 0); + info.ret_code = root.value("ret_code", 0); + info.create_on = root.value("create_on", ""); + info.err_msg = root.value("err_msg", ""); + } + + + // -------------------------------------------------------------------------- + // 1300 - robot_status_map_req 查询地图状态 + // -------------------------------------------------------------------------- + void AgvSrc1100::getMapStatus(MapStatus& info) + { + int sock = sock_status_; + if (sock < 0) + { + info.ret_code = -1; + info.err_msg = "socket not connected"; + return; + } + + // 命令码 0x0514 = 1300 + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x05, 0x14, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + if (!sendAndRecv(sock, send_buf)) + { + info.ret_code = -2; + info.err_msg = last_error_; + return; + } + + std::string json_str; + { + std::lock_guard lk(mutex_); + json_str = current_json_; + } + + if (!json_str.empty()) + { + if (json_str.front() != '{') json_str = "{" + json_str; + if (json_str.back() != '}') json_str += "}"; + } + + if (json_str.empty()) + { + info.ret_code = -4; + info.err_msg = "empty json data"; + return; + } + + json root; + std::string err; + if (!parseJson(json_str, root, err)) + { + info.ret_code = -3; + info.err_msg = err; + return; + } + + // 提取地图字段 + info.current_map = root.value("current_map", ""); + info.current_map_md5 = root.value("current_map_md5", ""); + info.ret_code = root.value("ret_code", 0); + info.create_on = root.value("create_on", ""); + info.err_msg = root.value("err_msg", ""); + + // 解析地图名称数组 + if (root.contains("maps") && root["maps"].is_array()) + { + for (auto& item : root["maps"]) + { + info.maps.push_back(item.get()); + } + } + + // 解析地图文件详情数组 + if (root.contains("map_files_info") && root["map_files_info"].is_array()) + { + for (auto& item : root["map_files_info"]) + { + MapFileInfo fileInfo; + fileInfo.name = item.value("name", ""); + fileInfo.modified = item.value("modified", ""); + fileInfo.size = item.value("size", 0LL); + info.map_files_info.push_back(fileInfo); + } + } + } + + + // -------------------------------------------------------------------------- + // 1022 - robot_status_loadmap_req 查询地图载入状态 + // -------------------------------------------------------------------------- + void AgvSrc1100::queryLoadMapStatus(QueryLoadMapStatusRes& res) + { + int sock = sock_status_; + if (sock < 0) + { + res.ret_code = -1; + res.err_msg = "socket not connected"; + return; + } + + // 命令码 0x03FE = 1022,无负载,长度=0 + uint16_t payload_len = 0; + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, + static_cast((payload_len >> 8) & 0xFF), // 长度高字节 + static_cast(payload_len & 0xFF), // 长度低字节 + 0x03, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + setSocketTimeout(sock, 1000); // 1秒超时 + + // 发送帧头(有重试机制) + int total_sent = 0; + int retry_cnt = 3; + while (total_sent < 16 && retry_cnt > 0) + { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); + if (ret <= 0) + { + retry_cnt--; + usleep(10000); + continue; + } + total_sent += ret; + } + if (total_sent != 16) + { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send frame header failed"; + return; + } + + // 接收响应 + const int recv_buf_len = 65536; + std::vector recv_buf(recv_buf_len); + std::string raw_data; + int recv_ret = 0; + while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) + { + raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); + } + setSocketTimeout(sock, 0); + + if (raw_data.empty()) + { + res.ret_code = -2; + res.err_msg = "no response data"; + return; + } + + // 提取 JSON + size_t json_start = raw_data.find('{'); + size_t json_end = raw_data.rfind('}'); + std::string json_str; + if (json_start == std::string::npos) + { + res.ret_code = -4; + res.err_msg = "json not found in response"; + return; + } + if (json_end == std::string::npos) + { + json_str = raw_data.substr(json_start) + "}"; + } + else + { + json_str = raw_data.substr(json_start, json_end - json_start + 1); + } + + // 解析 JSON + json root; + std::string parse_err; + if (!parseJson(json_str, root, parse_err)) + { + res.ret_code = -3; + res.err_msg = "json parse error: " + parse_err; + return; + } + + // loadmap_status: 0=失败, 1=成功, 2=载入中 + res.loadmap_status = root.value("loadmap_status", 0); + res.ret_code = root.value("ret_code", 0); + res.create_on = root.value("create_on", ""); + res.err_msg = root.value("err_msg", ""); + } + + + // -------------------------------------------------------------------------- + // 1301 - robot_status_station_req 查询站点列表 + // -------------------------------------------------------------------------- + void AgvSrc1100::queryStationList(QueryStationRes& res) + { + int sock = sock_status_; + if (sock < 0) + { + res.ret_code = -1; + res.err_msg = "socket not connected"; + return; + } + + // 命令码 0x0515 = 1301,无负载,长度=0 + uint16_t payload_len = 0; + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, + static_cast((payload_len >> 8) & 0xFF), + static_cast(payload_len & 0xFF), + 0x05, 0x15, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + setSocketTimeout(sock, 1000); + + int total_sent = 0; + int retry_cnt = 3; + while (total_sent < 16 && retry_cnt > 0) + { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); + if (ret <= 0) + { + retry_cnt--; + usleep(10000); + continue; + } + total_sent += ret; + } + if (total_sent != 16) + { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send frame header failed"; + return; + } + + const int recv_buf_len = 65536; + std::vector recv_buf(recv_buf_len); + std::string raw_data; + int recv_ret = 0; + while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) + { + raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); + } + setSocketTimeout(sock, 0); + + if (raw_data.empty()) + { + res.ret_code = -2; + res.err_msg = "no response data"; + return; + } + + size_t json_start = raw_data.find('{'); + size_t json_end = raw_data.rfind('}'); + std::string json_str; + if (json_start == std::string::npos) + { + res.ret_code = -4; + res.err_msg = "json not found in response"; + return; + } + if (json_end == std::string::npos) + { + json_str = raw_data.substr(json_start) + "}"; + } + else + { + json_str = raw_data.substr(json_start, json_end - json_start + 1); + } + + json root; + std::string parse_err; + if (!parseJson(json_str, root, parse_err)) + { + res.ret_code = -3; + res.err_msg = "json parse error: " + parse_err; + return; + } + + res.ret_code = root.value("ret_code", 0); + res.create_on = root.value("create_on", ""); + res.err_msg = root.value("err_msg", ""); + + // 解析站点数组 + if (root.contains("stations") && root["stations"].is_array()) + { + for (auto& item : root["stations"]) + { + StationItem st; + st.id = item.value("id", ""); + st.type = item.value("type", ""); + st.x = item.value("x", 0.0); + st.y = item.value("y", 0.0); + st.r = item.value("r", 0.0); + st.desc = item.value("desc", ""); + st.executor = item.value("executor", ""); + st.prepoint = item.value("prepoint", ""); + st.recfile = item.value("recfile", ""); + + // 兼容 bool 和字符串 "true"/"false" + if (item.contains("spin")) + { + if (item["spin"].is_boolean()) + st.spin = item["spin"].get(); + else if (item["spin"].is_string()) + st.spin = (item["spin"].get() == "true"); + } + if (item.contains("use_down_pgv")) + { + if (item["use_down_pgv"].is_boolean()) + st.use_down_pgv = item["use_down_pgv"].get(); + else if (item["use_down_pgv"].is_string()) + st.use_down_pgv = (item["use_down_pgv"].get() == "true"); + } + + res.stations.push_back(st); + } + } + } + + + // -------------------------------------------------------------------------- + // 1060 - robot_status_current_lock_req 查询当前控制权所有者 + // -------------------------------------------------------------------------- + void AgvSrc1100::getCurrentLockStatus(CurrentLockStatus& info) + { + int sock = sock_status_; + if (sock < 0) + { + info.ret_code = -1; + info.err_msg = "socket not connected"; + return; + } + + // 命令码 0x0424 = 1060,无负载,长度=0 + uint16_t payload_len = 0; + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, + static_cast((payload_len >> 8) & 0xFF), + static_cast(payload_len & 0xFF), + 0x04, 0x24, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + setSocketTimeout(sock, 1000); + + int total_sent = 0; + int retry = 3; + while (total_sent < 16 && retry > 0) + { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, 0); + if (ret <= 0) + { + retry--; + usleep(10000); + continue; + } + total_sent += ret; + } + if (total_sent != 16) { setSocketTimeout(sock, 0); info.ret_code = -2; - info.err_msg = "send map payload failed"; + info.err_msg = "send frame header failed"; return; } - offset += ret; - usleep(500); - } - // 接收响应 - const int RECV_BUF_LEN = 65536; - std::vector recvBuf(RECV_BUF_LEN); - std::string rawData; - int recvRet = 0; - while ((recvRet = recv(sock, recvBuf.data(), RECV_BUF_LEN, 0)) > 0) - { - rawData.append(reinterpret_cast(recvBuf.data()), recvRet); - } - setSocketTimeout(sock, 0); - - if (rawData.empty()) - { - info.ret_code = -2; - info.err_msg = "no response data"; - return; - } - - // 提取JSON - size_t jsonStart = rawData.find('{'); - size_t jsonEnd = rawData.rfind('}'); - std::string json_str; - if (jsonStart == std::string::npos) - { - info.ret_code = -4; - info.err_msg = "json not found"; - return; - } - if (jsonEnd == std::string::npos) - { - json_str = rawData.substr(jsonStart) + "}"; - } - else - { - json_str = rawData.substr(jsonStart, jsonEnd - jsonStart + 1); - } - - // 解析JSON - json root; - std::string err; - if (!parseJson(json_str, root, err)) - { - info.ret_code = -3; - info.err_msg = "json parse error: " + err; - return; - } - - info.ret_code = root.value("ret_code", 0); - info.create_on = root.value("create_on", ""); - info.err_msg = root.value("err_msg", ""); -} - - // =========================================================================== -// 接口:抢占机器人控制权 命令码 4005 (0x0FA5) -// =========================================================================== -/** - * @brief 下发抢占控制权指令到机器人配置端口 - * @param info 输出抢占结果结构体 - * @param nick_name 抢占方标识名称 - */ -void AgvSrc1100::lockRobotControl(LockResult& info, const std::string& nick_name) -{ - // 使用配置端口发送控制指令 - int sock = sock_config_; - if (sock < 0) - { - info.ret_code = -1; - info.err_msg = "socket not connected"; - return; - } - - // 组装请求JSON:{"nick_name":"xxx"} - std::string req_json = R"({"nick_name":")" + nick_name + R"("})"; - // 负载长度双字节大端存储,兼容长字符串 - uint16_t payload_len = static_cast(req_json.size()); - - // 16字节协议帧头,严格对齐设备报文格式 - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, - static_cast((payload_len >> 8) & 0xFF), // 长度高字节 - static_cast(payload_len & 0xFF), // 长度低字节 - 0x0F, 0xA5, // 指令编号 0x0FA5=4005 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - // 清空socket残留脏数据,防止粘包干扰 - flushSocket(sock); - // 设置IO超时1000ms,适配网络延迟 - setSocketTimeout(sock, 1000); - - // 循环发送16字节帧头,最多重试3次 - int total_sent = 0; - int retry = 3; - while (total_sent < 16 && retry > 0) - { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, 0); - if (ret <= 0) + const int RECV_BUF_LEN = 65536; + std::vector recv_buf(RECV_BUF_LEN); + std::string raw_data; + int recv_ret = 0; + while ((recv_ret = recv(sock, recv_buf.data(), RECV_BUF_LEN, 0)) > 0) { - retry--; - usleep(10000); - continue; + raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); } - total_sent += ret; - } - // 帧头发送失败直接返回错误 - if (total_sent != 16) - { setSocketTimeout(sock, 0); - info.ret_code = -2; - info.err_msg = "send frame header failed"; - return; + + if (raw_data.empty()) + { + info.ret_code = -2; + info.err_msg = "no response data"; + return; + } + + size_t json_start = raw_data.find('{'); + size_t json_end = raw_data.rfind('}'); + std::string json_str; + if (json_start == std::string::npos) + { + info.ret_code = -4; + info.err_msg = "json not found in response"; + return; + } + if (json_end == std::string::npos) + { + json_str = raw_data.substr(json_start) + "}"; + } + else + { + json_str = raw_data.substr(json_start, json_end - json_start + 1); + } + + json root; + std::string err; + if (!parseJson(json_str, root, err)) + { + info.ret_code = -3; + info.err_msg = "json parse error: " + err; + return; + } + + // 提取控制权信息 + info.locked = root.value("locked", false); + info.ip = root.value("ip", ""); + info.port = root.value("port", 0); + info.type = static_cast(root.value("type", 0)); + info.nick_name = root.value("nick_name", ""); + info.time_t = root.value("time_t", 0LL); + info.desc = root.value("desc", ""); + info.ret_code = root.value("ret_code", 0); + info.create_on = root.value("create_on", ""); + info.err_msg = root.value("err_msg", ""); } - // 分片发送JSON负载,防止缓冲区溢出 - const int slice_size = 4096; - size_t offset = 0; - size_t total_size = req_json.size(); - const char* data = req_json.c_str(); - while (offset < total_size) + + // -------------------------------------------------------------------------- + // 1020 - robot_status_task_req 查询当前导航状态 + // -------------------------------------------------------------------------- + void AgvSrc1100::robotStatusTaskCurrent(RobotStatusTaskCurrentRes& res, const RobotStatusTaskCurrentReq& req) { - int send_len = std::min((size_t)slice_size, total_size - offset); - int ret = send(sock, data + offset, send_len, 0); - if (ret <= 0) + int sock = sock_status_; + LOG(INFO) << "robotStatusTaskCurrent sock fd=" << sock; + if (sock < 0) + { + res.ret_code = -1; + res.err_msg = "status port socket invalid"; + return; + } + + // 组装请求 JSON: {"simple": true/false} + json send_json; + send_json["simple"] = req.simple; + std::string payload = send_json.dump(); + uint16_t payload_len = static_cast(payload.size()); + + // 命令码 0x03FC = 1020 + static uint8_t frame_header[16] = { + 0x5A,0x01,0x00,0x01, + 0x00,0x00, + 0,0, + 0x03,0xFC, + 0,0,0,0,0,0 + }; + frame_header[6] = static_cast((payload_len >> 8) & 0xFF); + frame_header[7] = static_cast(payload_len & 0xFF); + + flushSocket(sock); + setSocketTimeout(sock, 3000); + + // 发送帧头 + int sent = 0; + int retry = 5; + while (sent < 16 && retry > 0) + { + int w = send(sock, frame_header + sent, 16 - sent, MSG_NOSIGNAL); + if (w <= 0) + { + retry--; + usleep(20000); + continue; + } + sent += w; + } + if (sent != 16) + { + res.ret_code = -2; + res.err_msg = "send frame header failed"; + return; + } + + // 分片发送 payload + const int slice = 1024; + size_t off = 0; + size_t total = payload.size(); + const char* data = payload.data(); + while (off < total) + { + int wlen = static_cast(std::min((size_t)slice, total - off)); + int w = send(sock, data + off, wlen, MSG_NOSIGNAL); + if (w <= 0) + { + res.ret_code = -2; + res.err_msg = "send payload failed"; + return; + } + off += w; + } + + // 接收响应 + std::string raw; + uint8_t buf[65536]; + int rlen = 0; + while ((rlen = recv(sock, buf, sizeof(buf), MSG_NOSIGNAL)) > 0) + { + raw.append((char*)buf, rlen); + } + if (raw.empty()) + { + res.ret_code = -2; + res.err_msg = "no response data"; + return; + } + + // 提取 JSON + size_t json_begin = raw.find('{'); + if (json_begin == std::string::npos) + { + res.ret_code = -4; + res.err_msg = "response no json"; + return; + } + std::string json_str = raw.substr(json_begin); + if (json_str.back() != '}') + json_str += "}"; + + json root; + try + { + root = json::parse(json_str); + } + catch (json::parse_error& e) + { + res.ret_code = -3; + res.err_msg = std::string("json parse err:") + e.what(); + return; + } + + // 提取公共字段 + res.ret_code = root.contains("ret_code") ? root["ret_code"].get() : 0; + res.create_on = root.contains("create_on") ? root["create_on"].get() : ""; + res.err_msg = root.contains("err_msg") ? root["err_msg"].get() : ""; + + // 提取导航状态字段 + // task_status: 0=NONE, 1=WAITING, 2=RUNNING, 3=SUSPENDED, 4=COMPLETED, 5=FAILED, 6=CANCELED + res.task_status = root.contains("task_status") ? root["task_status"].get() : 0; + // task_type: 0=无, 1=自由导航到坐标, 2=自由导航到站点, 3=路径导航到站点 + res.task_type = root.contains("task_type") ? root["task_type"].get() : 0; + res.target_id = root.contains("target_id") ? root["target_id"].get() : ""; + res.move_status_info = root.contains("move_status_info") ? root["move_status_info"].get() : ""; + + // 提取目标坐标 [x, y, r] + if (root.contains("target_point") && root["target_point"].is_array()) + { + for (auto& val : root["target_point"]) + res.target_point.push_back(val.get()); + } + + // 提取已经过的站点列表 + if (root.contains("finished_path") && root["finished_path"].is_array()) + { + for (auto& s : root["finished_path"]) + res.finished_path.push_back(s.get()); + } + + // 提取尚未经过的站点列表 + if (root.contains("unfinished_path") && root["unfinished_path"].is_array()) + { + for (auto& s : root["unfinished_path"]) + res.unfinished_path.push_back(s.get()); + } + + // 提取料箱信息 + if (root.contains("containers") && root["containers"].is_array()) + { + for (auto& item : root["containers"]) + { + NavContainerItem c; + c.container_name = item.contains("container_name") ? item["container_name"].get() : ""; + c.desc = item.contains("desc") ? item["desc"].get() : ""; + c.goods_id = item.contains("goods_id") ? item["goods_id"].get() : ""; + c.has_goods = item.contains("has_goods") ? item["has_goods"].get() : false; + res.containers.push_back(c); + } + } + } + + + // -------------------------------------------------------------------------- + // 1110 - robot_status_task_status_package_req 批量查询任务状态 + // -------------------------------------------------------------------------- + void AgvSrc1100::robotStatusTaskPackage(QueryTaskStatusPackageRes& res, const QueryTaskStatusPackageReq& req) + { + int sock = sock_status_; + LOG(INFO) << "robotStatusTaskPackage sock fd=" << sock; + if (sock < 0) + { + res.ret_code = -1; + res.err_msg = "status port socket invalid"; + return; + } + + // 强制携带 task_ids 字段,空数组也生成,避免设备报 60002 错误 + json send_json; + json arr = json::array(); + for (auto& tid : req.task_ids) + arr.push_back(tid); + send_json["task_ids"] = arr; + + std::string payload = send_json.dump(); + uint16_t payload_len = static_cast(payload.size()); + + // 命令码 0x0456 = 1110 + static uint8_t frame_header[16] = { + 0x5A,0x01,0x00,0x01, + 0x00,0x00, + 0,0, + 0x04,0x56, + 0,0,0,0,0,0 + }; + frame_header[6] = static_cast((payload_len >> 8) & 0xFF); + frame_header[7] = static_cast(payload_len & 0xFF); + + flushSocket(sock); + setSocketTimeout(sock, 3000); + + // 发送帧头 + int sent = 0; + int retry = 5; + while (sent < 16 && retry > 0) + { + int w = send(sock, frame_header + sent, 16 - sent, MSG_NOSIGNAL); + if (w <= 0) + { + retry--; + usleep(20000); + continue; + } + sent += w; + } + if (sent != 16) + { + res.ret_code = -2; + res.err_msg = "send frame header failed"; + return; + } + + // 分片发送 payload + const int slice = 1024; + size_t off = 0; + size_t total = payload.size(); + const char* data = payload.data(); + while (off < total) + { + int wlen = static_cast(std::min((size_t)slice, total - off)); + int w = send(sock, data + off, wlen, MSG_NOSIGNAL); + if (w <= 0) + { + res.ret_code = -2; + res.err_msg = "send payload failed"; + return; + } + off += w; + } + + // 接收响应 + std::string raw; + uint8_t buf[65536]; + int rlen = 0; + while ((rlen = recv(sock, buf, sizeof(buf), MSG_NOSIGNAL)) > 0) + { + raw.append((char*)buf, rlen); + } + if (raw.empty()) + { + res.ret_code = -2; + res.err_msg = "no response data"; + return; + } + + // 提取 JSON + size_t json_begin = raw.find('{'); + if (json_begin == std::string::npos) + { + res.ret_code = -4; + res.err_msg = "response no json"; + return; + } + std::string json_str = raw.substr(json_begin); + if (json_str.back() != '}') + json_str += "}"; + + json root; + try + { + root = json::parse(json_str); + } + catch (json::parse_error& e) + { + res.ret_code = -3; + res.err_msg = std::string("json parse err:") + e.what(); + return; + } + + // 提取公共字段 + res.ret_code = root.contains("ret_code") ? root["ret_code"].get() : 0; + res.create_on = root.contains("create_on") ? root["create_on"].get() : ""; + res.err_msg = root.contains("err_msg") ? root["err_msg"].get() : ""; + + // 解析 task_status_package 对象 + if (root.contains("task_status_package")) + { + auto pkg_json = root["task_status_package"]; + auto& pkg_out = res.task_status_package; + + pkg_out.closest_target = pkg_json.contains("closest_target") ? pkg_json["closest_target"].get() : ""; + pkg_out.source_name = pkg_json.contains("source_name") ? pkg_json["source_name"].get() : ""; + pkg_out.target_name = pkg_json.contains("target_name") ? pkg_json["target_name"].get() : ""; + pkg_out.percentage = pkg_json.contains("percentage") ? pkg_json["percentage"].get() : 0.0; + pkg_out.distance = pkg_json.contains("distance") ? pkg_json["distance"].get() : 0.0; + pkg_out.info = pkg_json.contains("info") ? pkg_json["info"].get() : ""; + + // 解析任务状态列表 + if (pkg_json.contains("task_status_list") && pkg_json["task_status_list"].is_array()) + { + for (auto& item : pkg_json["task_status_list"]) + { + SingleTaskStatusItem st; + st.task_id = item.contains("task_id") ? item["task_id"].get() : ""; + st.status = item.contains("status") ? item["status"].get() : 0; + st.type = item.contains("type") ? item["type"].get() : 0; + pkg_out.task_status_list.push_back(st); + } + } + } + } + + + // ============================================================================ + // 端口 19205 – 机器人控制 API(允许 5 个连接) + // 功能:下发控制指令,改变机器人运动或状态 + // ============================================================================ + + // -------------------------------------------------------------------------- + // 2010 - robot_control_motion_req 开环速度运动控制 + // -------------------------------------------------------------------------- + void AgvSrc1100::robotMotionControl(MotionCtrlRes& res, const MotionCtrlReq& req) + { + int sock = sock_control_; + if (sock < 0) + { + res.ret_code = -1; + res.err_msg = "socket not connected"; + return; + } + + // 组装请求 JSON,只包含非零有效字段 + json send_json; + if (req.vx != 0.0) send_json["vx"] = req.vx; // X 方向速度 + if (req.vy != 0.0) send_json["vy"] = req.vy; // Y 方向速度 + if (req.w != 0.0) send_json["w"] = req.w; // 角速度 + if (req.steer != 0.0) send_json["steer"] = req.steer; // 舵角(单舵轮) + if (req.real_steer != 0.0) send_json["real_steer"] = req.real_steer; // 实际舵角 + if (req.duration >= 0) send_json["duration"] = req.duration; // 运动持续时间 + + std::string payload = send_json.dump(); + uint16_t payload_len = static_cast(payload.size()); + + // 命令码 0x07DA = 2010 + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, + static_cast((payload_len >> 8) & 0xFF), + static_cast(payload_len & 0xFF), + 0x07, 0xDA, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + setSocketTimeout(sock, 1000); + + int total_sent = 0; + int retry_cnt = 3; + while (total_sent < 16 && retry_cnt > 0) + { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); + if (ret <= 0) + { + retry_cnt--; + usleep(10000); + continue; + } + total_sent += ret; + } + if (total_sent != 16) { setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send frame header failed"; + return; + } + + // 分片发送 payload + const int slice_size = 4096; + size_t offset = 0; + size_t total_size = payload.size(); + const char* data_ptr = payload.data(); + while (offset < total_size) + { + int send_len = std::min((size_t)slice_size, total_size - offset); + int ret = send(sock, data_ptr + offset, send_len, MSG_NOSIGNAL); + if (ret <= 0) + { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send payload failed"; + return; + } + offset += ret; + usleep(500); + } + + // 接收响应 + const int recv_buf_len = 65536; + std::vector recv_buf(recv_buf_len); + std::string raw_data; + int recv_ret = 0; + while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) + { + raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); + } + setSocketTimeout(sock, 0); + + if (raw_data.empty()) + { + res.ret_code = -2; + res.err_msg = "no response data"; + return; + } + + // 提取并解析 JSON + size_t json_start = raw_data.find('{'); + size_t json_end = raw_data.rfind('}'); + std::string json_str; + if (json_start == std::string::npos) + { + res.ret_code = -4; + res.err_msg = "json not found in response"; + return; + } + if (json_end == std::string::npos) + { + json_str = raw_data.substr(json_start) + "}"; + } + else + { + json_str = raw_data.substr(json_start, json_end - json_start + 1); + } + + json root; + std::string parse_err; + if (!parseJson(json_str, root, parse_err)) + { + res.ret_code = -3; + res.err_msg = "json parse error: " + parse_err; + return; + } + + res.ret_code = root.value("ret_code", 0); + res.create_on = root.value("create_on", ""); + res.err_msg = root.value("err_msg", ""); + } + + + // -------------------------------------------------------------------------- + // 2022 - robot_control_loadmap_req 切换载入地图 + // -------------------------------------------------------------------------- + void AgvSrc1100::robotLoadMap(LoadMapRes& res, const LoadMapReq& req) + { + int sock = sock_control_; + if (sock < 0) + { + res.ret_code = -1; + res.err_msg = "socket not connected"; + return; + } + + // 组装请求 JSON: {"map_name":"xxx"} + json send_json; + send_json["map_name"] = req.map_name; + std::string payload = send_json.dump(); + uint16_t payload_len = static_cast(payload.size()); + + // 命令码 0x07E6 = 2022 + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, + static_cast((payload_len >> 8) & 0xFF), + static_cast(payload_len & 0xFF), + 0x07, 0xE6, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + setSocketTimeout(sock, 1000); + + // 发送帧头 + int total_sent = 0; + int retry_cnt = 3; + while (total_sent < 16 && retry_cnt > 0) + { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); + if (ret <= 0) + { + retry_cnt--; + usleep(10000); + continue; + } + total_sent += ret; + } + if (total_sent != 16) + { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send frame header failed"; + return; + } + + // 分片发送 payload + const int slice_size = 4096; + size_t offset = 0; + size_t total_size = payload.size(); + const char* data_ptr = payload.data(); + while (offset < total_size) + { + int send_len = std::min((size_t)slice_size, total_size - offset); + int ret = send(sock, data_ptr + offset, send_len, MSG_NOSIGNAL); + if (ret <= 0) + { + setSocketTimeout(sock, 3000); + res.ret_code = -2; + res.err_msg = "send payload failed"; + return; + } + offset += ret; + usleep(500); + } + + // 接收响应 + const int recv_buf_len = 65536; + std::vector recv_buf(recv_buf_len); + std::string raw_data; + int recv_ret = 0; + while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) + { + raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); + } + setSocketTimeout(sock, 0); + + if (raw_data.empty()) + { + res.ret_code = -2; + res.err_msg = "no response data"; + return; + } + + // 提取并解析 JSON + size_t json_start = raw_data.find('{'); + size_t json_end = raw_data.rfind('}'); + std::string json_str; + if (json_start == std::string::npos) + { + res.ret_code = -4; + res.err_msg = "json not found in response"; + return; + } + if (json_end == std::string::npos) + { + json_str = raw_data.substr(json_start) + "}"; + } + else + { + json_str = raw_data.substr(json_start, json_end - json_start + 1); + } + + json root; + std::string parse_err; + if (!parseJson(json_str, root, parse_err)) + { + res.ret_code = -3; + res.err_msg = "json parse error: " + parse_err; + return; + } + + res.ret_code = root.value("ret_code", 0); + res.create_on = root.value("create_on", ""); + res.err_msg = root.value("err_msg", ""); + LOG(INFO) << "recv raw_data len=" << raw_data.size() << " content=" << raw_data; + } + + + // -------------------------------------------------------------------------- + // 2000 - robot_control_stop_req 停止开环运动 命令码0x07D0,应答码12000(0x2EE0) + // 下发零速度开环指令,终止当前持续运动 + // -------------------------------------------------------------------------- + // -------------------------------------------------------------------------- + // 2000 - robot_control_stop_req 停止开环运动 命令码0x07D0,应答码12000(0x2EE0) + // 下发零速度开环指令,终止当前持续运动 + // -------------------------------------------------------------------------- + + void AgvSrc1100::robotControlStop(RobotControlStopRes& res) + { + int sock = sock_control_; + if (sock < 0) { + res.ret_code = -1; + res.err_msg = "control socket disconnected"; + return; + } + + // 业务负载长度为 0 + uint32_t payload_len = 0; + + // 标准 16 字节帧头(长度 4 字节,命令码 0x07D0,保留 6 字节) + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + static_cast((payload_len >> 24) & 0xFF), + static_cast((payload_len >> 16) & 0xFF), + static_cast((payload_len >> 8) & 0xFF), + static_cast(payload_len & 0xFF), + 0x07, 0xD0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + setSocketTimeout(sock, 1000); // 适当增加超时 + + // 发送帧头(重试机制) + int total_sent = 0; + int retry = 3; + while (total_sent < 16 && retry-- > 0) { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); + if (ret <= 0) { + usleep(10000); + continue; + } + total_sent += ret; + } + if (total_sent != 16) { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send header failed"; + return; + } + + // 无 payload,无需发送额外数据 + + // 接收响应(使用较大缓冲区) + const int RECV_BUF = 131072; + std::vector recv_buf(RECV_BUF); + std::string raw_data; + int rlen; + while ((rlen = recv(sock, recv_buf.data(), RECV_BUF, MSG_NOSIGNAL)) > 0) { + raw_data.append(reinterpret_cast(recv_buf.data()), rlen); + } + setSocketTimeout(sock, 0); + + if (raw_data.empty()) { + res.ret_code = -2; + res.err_msg = "no response from device"; + return; + } + + // 提取 JSON(容错补全) + size_t json_start = raw_data.find('{'); + if (json_start == std::string::npos) { + res.ret_code = -4; + res.err_msg = "no JSON in response"; + return; + } + std::string json_str = raw_data.substr(json_start); + // 补全缺失的 '}' + int left = std::count(json_str.begin(), json_str.end(), '{'); + int right = std::count(json_str.begin(), json_str.end(), '}'); + while (right < left) { json_str += "}"; right++; } + + json root; + std::string parse_err; + if (!parseJson(json_str, root, parse_err)) { + res.ret_code = -3; + res.err_msg = "JSON parse error: " + parse_err; + return; + } + + res.ret_code = root.value("ret_code", 0); + res.create_on = root.value("create_on", ""); + res.err_msg = root.value("err_msg", ""); + } + + + // ============================================================================ + // 端口 19206 – 机器人导航 API(允许 5 个连接) + // 功能:下发导航任务 + // ============================================================================ + + // -------------------------------------------------------------------------- + // 3066 - robot_task_gotargetlist_req 指定路径导航(多点) + // 功能:下发多段连续站点导航任务序列,任务串行排队执行 + // 必填字段:task_id / source_id / id + // -------------------------------------------------------------------------- + void AgvSrc1100::robotGoTargetList(GoTargetListRes& res, const GoTargetListReq& req) + { + int sock = sock_nav_; + LOG(INFO) << "robotGoTargetList sock fd=" << sock; + if (sock < 0) + { + res.ret_code = -1; + res.err_msg = "nav socket invalid"; + return; + } + + // 组装请求 JSON: {"move_task_list": [{"task_id":"xxx","source_id":"xxx","id":"xxx"}, ...]} + json send_json; + json arr = json::array(); + for (const auto& task : req.move_task_list) + { + json obj; + obj["task_id"] = task.task_id; + obj["source_id"] = task.source_id; + obj["id"] = task.id; + if (!task.operation.empty()) + { + obj["operation"] = task.operation; + obj["jack_height"] = task.jack_height; + } + arr.push_back(obj); + } + send_json["move_task_list"] = arr; + std::string payload = send_json.dump(); + uint16_t payload_len = static_cast(payload.size()); + LOG(INFO) << "nav payload length:" << payload_len; + + // 命令码 0x0BFA = 3066 + static uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, + 0,0, + 0x0B, 0xFA, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + send_buf[6] = static_cast((payload_len >> 8) & 0xFF); + send_buf[7] = static_cast(payload_len & 0xFF); + + flushSocket(sock); + setSocketTimeout(sock, 3000); + + // 发送帧头,带重试 + int total_sent = 0; + int retry_cnt = 5; + while (total_sent < 16 && retry_cnt > 0) + { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); + if (ret <= 0) + { + LOG(ERROR) << "send header failed, ret=" << ret + << ", errno=" << errno + << " (" << strerror(errno) << ")"; + retry_cnt--; + usleep(20000); + continue; + } + total_sent += ret; + } + if (total_sent != 16) + { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send frame header failed"; + return; + } + + // 分片发送 payload(1024 字节一片) + const int slice_size = 1024; + size_t offset = 0; + size_t total_size = payload.size(); + const char* data_ptr = payload.data(); + while (offset < total_size) + { + int send_len = std::min((size_t)slice_size, total_size - offset); + int ret = send(sock, data_ptr + offset, send_len, MSG_NOSIGNAL); + if (ret <= 0) + { + LOG(ERROR) << "send payload failed, errno=" << errno << " (" << strerror(errno) << ")"; + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send payload failed"; + return; + } + offset += ret; + } + + // 接收响应 + const int recv_buf_len = 65536; + std::vector recv_buf(recv_buf_len); + std::string raw_data; + int recv_ret = 0; + while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, MSG_NOSIGNAL)) > 0) + { + raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); + } + setSocketTimeout(sock, 0); + + if (raw_data.empty()) + { + res.ret_code = -2; + res.err_msg = "no response data"; + return; + } + + // 提取并解析 JSON + size_t json_start = raw_data.find('{'); + if (json_start == std::string::npos) + { + res.ret_code = -4; + res.err_msg = "json not found in response"; + return; + } + std::string json_str = raw_data.substr(json_start); + if (json_str.back() != '}') + { + json_str += "}"; + } + + json root; + std::string parse_err; + try + { + root = json::parse(json_str); + } + catch (json::parse_error& e) + { + res.ret_code = -3; + res.err_msg = "json parse error: " + std::string(e.what()); + return; + } + + res.ret_code = root.contains("ret_code") ? root["ret_code"].get() : 0; + res.create_on = root.contains("create_on") ? root["create_on"].get() : ""; + res.err_msg = root.contains("err_msg") ? root["err_msg"].get() : ""; + } + + + // -------------------------------------------------------------------------- + // 3051 - robot_task_gotarget_req 单点路径导航(自动规划) + // 功能:给定目标站点,机器人自动规划路径前往 + // 注意:仅用于单车测试,严禁用于多车调度场景 + // -------------------------------------------------------------------------- + void AgvSrc1100::robotGoTarget(RobotGoTargetRes& res, const RobotGoTargetReq& req) + { + int sock = sock_nav_; + LOG(INFO) << "robotGoTarget control sock fd=" << sock; + if (sock < 0) + { + res.ret_code = -1; + res.err_msg = "control port socket invalid"; + return; + } + + // 组装请求 JSON(根据协议 3051 的字段结构) + json send_json; + // 必填字段 + send_json["source_id"] = req.source_id; + send_json["id"] = req.id; + if (!req.task_id.empty()) + send_json["task_id"] = req.task_id; + + // 基础运动参数(只添加非默认值) + if (req.angle != 0) send_json["angle"] = req.angle; + if (!req.method.empty()) send_json["method"] = req.method; + if (req.max_speed != 0) send_json["max_speed"] = req.max_speed; + if (req.max_wspeed != 0) send_json["max_wspeed"] = req.max_wspeed; + if (req.max_acc != 0) send_json["max_acc"] = req.max_acc; + if (req.max_wacc != 0) send_json["max_wacc"] = req.max_wacc; + if (req.duration > 0) send_json["duration"] = req.duration; + if (req.orientation != 0) send_json["orientation"] = req.orientation; + if (req.spin) send_json["spin"] = true; + if (req.delay > 0) send_json["delay"] = req.delay; + if (req.start_rot_dir != 0) send_json["start_rot_dir"] = req.start_rot_dir; + if (req.end_rot_dir != 0) send_json["end_rot_dir"] = req.end_rot_dir; + if (req.reach_dist != 0) send_json["reach_dist"] = req.reach_dist; + if (req.reach_angle != 0) send_json["reach_angle"] = req.reach_angle; + if (!req.skill_name.empty()) send_json["skill_name"] = req.skill_name; + + // PGV 二次定位参数 + auto& pgv = req.pgv; + if (pgv.use_pgv) send_json["use_pgv"] = true; + if (pgv.use_down_pgv) send_json["use_down_pgv"] = true; + if (pgv.pgv_adjust_dist != 0) send_json["pgv_adjust_dist"] = pgv.pgv_adjust_dist; + if (pgv.pgv_adjust_cx != 0) send_json["pgv_adjust_cx"] = pgv.pgv_adjust_cx; + if (pgv.pgv_adjust_cy != 0) send_json["pgv_adjust_cy"] = pgv.pgv_adjust_cy; + if (pgv.pgv_x_adjust != 0) send_json["pgv_x_adjust"] = pgv.pgv_x_adjust; + + // 设备操作(顶升/货叉/辊筒/牵引/音频/等待DI/设置DO/脚本) + if (!req.operation.empty()) + { + send_json["operation"] = req.operation; + + // 顶升设备 JackLoad/JackUnload/JackHeight + if (req.operation.substr(0,4) == "Jack") + { + if (req.jack_height != 0) send_json["jack_height"] = req.jack_height; + } + // 货叉设备 ForkLoad/ForkUnload/ForkHeight/ForkForward + else if (req.operation.substr(0,4) == "Fork") + { + if (req.start_height != 0) send_json["start_height"] = req.start_height; + if (req.end_height != 0) send_json["end_height"] = req.end_height; + if (req.fork_mid_height != 0) send_json["fork_mid_height"] = req.fork_mid_height; + if (req.fork_dist != 0) send_json["fork_dist"] = req.fork_dist; + } + // 辊筒设备 RollerLoad/RollerUnload/RollerRoll/... + else if (req.operation.substr(0,6) == "Roller") + { + if (!req.direction.empty()) send_json["direction"] = req.direction; + } + // 牵引设备 HookLoad/HookUnload + else if (req.operation.substr(0,4) == "Hook") + { + if (req.recognize) send_json["recognize"] = true; + } + // 音频设备 sound + else if (req.operation == "sound") + { + json sound; + auto& sa = req.sounds_args; + if (!sa.name.empty()) sound["name"] = sa.name; + if (sa.loop != 0) sound["loop"] = sa.loop; + if (sa.stop != 0) sound["stop"] = sa.stop; + send_json["sounds_args"] = sound; + } + // 等待 DI 触发 + else if (req.operation == "WaitDI") + { + json args_obj; + json di_arr = json::array(); + for (auto& di : req.wait_di_args.DI) + { + json di_item; + di_item["id"] = di.id; + di_item["status"] = di.status; + di_arr.push_back(di_item); + } + args_obj["DI"] = di_arr; + if (req.wait_di_args.timeout > 0) args_obj["timeout"] = req.wait_di_args.timeout; + send_json["args"] = args_obj; + } + // 控制 DO 开关 + else if (req.operation == "SetDO") + { + json args_obj; + json do_arr = json::array(); + for (auto& d : req.set_do_args.DO) + { + json do_item; + do_item["id"] = d.id; + do_item["status"] = d.status; + do_arr.push_back(do_item); + } + args_obj["DO"] = do_arr; + send_json["args"] = args_obj; + } + } + + // 视觉识别标志 + if (req.recognize) send_json["recognize"] = true; + if (!req.recfile.empty()) send_json["recfile"] = req.recfile; + + // 脚本执行 + if (!req.script_name.empty()) + { + send_json["script_name"] = req.script_name; + send_json["script_stage"] = req.script_stage; + json s_arg; + for (auto& kv : req.script_args.str_kv) s_arg[kv.first] = kv.second; + for (auto& kv : req.script_args.num_kv) s_arg[kv.first] = kv.second; + json do_arr = json::array(); + for (auto& d : req.script_args.do_list) + { + json o; o["id"] = d.id; o["status"] = d.status; do_arr.push_back(o); + } + if (!do_arr.empty()) s_arg["DO"] = do_arr; + json di_arr = json::array(); + for (auto& di : req.script_args.di_list) + { + json o; o["id"] = di.id; o["status"] = di.status; di_arr.push_back(o); + } + if (!di_arr.empty()) s_arg["DI"] = di_arr; + send_json["script_args"] = s_arg; + } + + // 原地旋转(GoByOdometer) + if (req.move_angle != 0) + { + send_json["move_angle"] = req.move_angle; + send_json["speed_w"] = req.speed_w; + send_json["loc_mode"] = req.loc_mode; + } + + // 自由导航(仅双轮差速底盘) + if (req.freeGo.x != 0 || req.freeGo.y != 0) + { + json fg; + fg["x"] = req.freeGo.x; + fg["y"] = req.freeGo.y; + fg["theta"] = req.freeGo.theta; + send_json["freeGo"] = fg; + } + + std::string payload = send_json.dump(); + uint16_t payload_len = static_cast(payload.size()); + + // 命令码 0x0BEB = 3051 + uint8_t frame_header[16] = { + 0x5A,0x01,0x00,0x01, + 0x00,0x00, + 0,0, + 0x0B,0xEB, + 0,0,0,0,0,0 + }; + frame_header[6] = static_cast((payload_len >> 8) & 0xFF); + frame_header[7] = static_cast(payload_len & 0xFF); + + flushSocket(sock); + setSocketTimeout(sock, 3000); + + // 发送帧头 + int sent = 0; + int retry = 5; + while (sent < 16 && retry > 0) + { + int w = send(sock, frame_header + sent, 16 - sent, MSG_NOSIGNAL); + if (w <= 0) + { + retry--; + usleep(20000); + continue; + } + sent += w; + } + if (sent != 16) + { + res.ret_code = -2; + res.err_msg = "send frame header failed"; + return; + } + + // 分片发送 payload + const int slice = 1024; + size_t off = 0; + size_t total = payload.size(); + const char* data = payload.data(); + while (off < total) + { + int wlen = static_cast(std::min((size_t)slice, total - off)); + int w = send(sock, data + off, wlen, MSG_NOSIGNAL); + if (w <= 0) + { + res.ret_code = -2; + res.err_msg = "send payload failed"; + return; + } + off += w; + } + + // 接收响应 + std::string raw; + uint8_t buf[65536]; + int rlen = 0; + while ((rlen = recv(sock, buf, sizeof(buf), MSG_NOSIGNAL)) > 0) + { + raw.append((char*)buf, rlen); + } + if (raw.empty()) + { + res.ret_code = -2; + res.err_msg = "no response data"; + return; + } + + // 提取并解析 JSON + size_t json_start = raw.find('{'); + if (json_start == std::string::npos) + { + res.ret_code = -4; + res.err_msg = "response no valid json"; + return; + } + std::string json_str = raw.substr(json_start); + if (json_str.back() != '}') json_str += "}"; + + json root; + try + { + root = json::parse(json_str); + } + catch (json::parse_error& e) + { + res.ret_code = -3; + res.err_msg = std::string("json parse err:") + e.what(); + return; + } + + res.ret_code = root.contains("ret_code") ? root["ret_code"].get() : 0; + res.create_on = root.contains("create_on") ? root["create_on"].get() : ""; + res.err_msg = root.contains("err_msg") ? root["err_msg"].get() : ""; + } + + + // ============================================================================ + // 端口 19207 – 机器人配置 API(允许 5 个连接) + // 功能:配置类操作(地图上传下载、控制权管理等) + // ============================================================================ + + // -------------------------------------------------------------------------- + // 4005 - robot_config_lock_req 抢占控制权 + // 功能:向机器人申请独占控制权,成功后获得操作权限 + // -------------------------------------------------------------------------- + void AgvSrc1100::lockRobotControl(LockResult& info, const std::string& nick_name) + { + int sock = sock_config_; + if (sock < 0) + { + info.ret_code = -1; + info.err_msg = "socket not connected"; + return; + } + + // 组装请求 JSON: {"nick_name":"xxx"} + std::string req_json = R"({"nick_name":")" + nick_name + R"("})"; + uint16_t payload_len = static_cast(req_json.size()); + + // 命令码 0x0FA5 = 4005 + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + 0x00, 0x00, + static_cast((payload_len >> 8) & 0xFF), + static_cast(payload_len & 0xFF), + 0x0F, 0xA5, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + setSocketTimeout(sock, 1000); + + // 发送帧头 + int total_sent = 0; + int retry = 3; + while (total_sent < 16 && retry > 0) + { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, 0); + if (ret <= 0) + { + retry--; + usleep(10000); + continue; + } + total_sent += ret; + } + if (total_sent != 16) + { + setSocketTimeout(sock, 0); + info.ret_code = -2; + info.err_msg = "send frame header failed"; + return; + } + + // 分片发送 JSON 负载 + const int slice_size = 4096; + size_t offset = 0; + size_t total_size = req_json.size(); + const char* data = req_json.c_str(); + while (offset < total_size) + { + int send_len = std::min((size_t)slice_size, total_size - offset); + int ret = send(sock, data + offset, send_len, 0); + if (ret <= 0) + { + setSocketTimeout(sock, 0); + info.ret_code = -2; + info.err_msg = "send payload failed"; + return; + } + offset += ret; + usleep(500); + } + + // 接收响应 + const int recv_buf_len = 65536; + std::vector recv_buf(recv_buf_len); + std::string raw_data; + int recv_ret = 0; + while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) + { + raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); + } + setSocketTimeout(sock, 0); + + if (raw_data.empty()) + { + info.ret_code = -2; + info.err_msg = "no response data"; + return; + } + + // 提取并解析 JSON + size_t json_start = raw_data.find('{'); + size_t json_end = raw_data.rfind('}'); + std::string json_str; + if (json_start == std::string::npos) + { + info.ret_code = -4; + info.err_msg = "json not found in response"; + return; + } + if (json_end == std::string::npos) + { + json_str = raw_data.substr(json_start) + "}"; + } + else + { + json_str = raw_data.substr(json_start, json_end - json_start + 1); + } + + json root; + std::string err; + if (!parseJson(json_str, root, err)) + { + info.ret_code = -3; + info.err_msg = "json parse error: " + err; + return; + } + + info.ret_code = root.value("ret_code", 0); + info.create_on = root.value("create_on", ""); + info.err_msg = root.value("err_msg", ""); + } + + + + // ============================================================================ + // 3003 - robot_task_cancel_req 取消当前导航任务 (导航端口 19206) + // ============================================================================ + void AgvSrc1100::robotTaskCancel(RobotTaskCancelRes& res) { + int sock = sock_nav_; + if (sock < 0) { + res.ret_code = -1; + res.err_msg = "nav socket disconnected"; + return; + } + + uint32_t payload_len = 0; + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + static_cast((payload_len >> 24) & 0xFF), + static_cast((payload_len >> 16) & 0xFF), + static_cast((payload_len >> 8) & 0xFF), + static_cast(payload_len & 0xFF), + 0x0B, 0xBB, // 命令码 3003 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + setSocketTimeout(sock, 1000); + + int total_sent = 0; + int retry = 3; + while (total_sent < 16 && retry-- > 0) { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); + if (ret <= 0) { + usleep(10000); + continue; + } + total_sent += ret; + } + if (total_sent != 16) { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send header failed"; + return; + } + + // 接收响应(先读 16 字节头,再按长度读 JSON) + uint8_t recv_header[16]; + size_t header_read = 0; + while (header_read < 16) { + int ret = recv(sock, recv_header + header_read, 16 - header_read, MSG_NOSIGNAL); + if (ret <= 0) { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "recv header failed or timeout"; + return; + } + header_read += ret; + } + + uint32_t data_len = (recv_header[4] << 24) | + (recv_header[5] << 16) | + (recv_header[6] << 8) | + recv_header[7]; + + std::string json_str; + if (data_len > 0) { + json_str.resize(data_len); + size_t total_read = 0; + while (total_read < data_len) { + int ret = recv(sock, &json_str[total_read], data_len - total_read, MSG_NOSIGNAL); + if (ret <= 0) { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "recv payload failed or timeout"; + return; + } + total_read += ret; + } + } + setSocketTimeout(sock, 0); + + if (json_str.empty()) { + res.ret_code = -4; + res.err_msg = "empty json data"; + return; + } + + // 容错提取 JSON + size_t json_start = json_str.find('{'); + if (json_start == std::string::npos) { + res.ret_code = -4; + res.err_msg = "no JSON found in payload"; + return; + } + size_t json_end = json_str.rfind('}'); + if (json_end != std::string::npos) { + json_str = json_str.substr(json_start, json_end - json_start + 1); + } else { + json_str = json_str.substr(json_start) + "}"; + } + + json root; + std::string parse_err; + if (!parseJson(json_str, root, parse_err)) { + res.ret_code = -3; + res.err_msg = "JSON parse error: " + parse_err; + return; + } + + res.ret_code = root.value("ret_code", 0); + res.create_on = root.value("create_on", ""); + res.err_msg = root.value("err_msg", ""); + + LOG(INFO) << "robotTaskCancel success, ret_code=" << res.ret_code; + } + + // ============================================================================ + // 3002 - robot_task_resume_req 继续当前导航任务 (导航端口 19206) + // ============================================================================ + void AgvSrc1100::robotTaskResume(RobotTaskResumeRes& res) { + int sock = sock_nav_; + if (sock < 0) { + res.ret_code = -1; + res.err_msg = "nav socket disconnected"; + return; + } + + uint32_t payload_len = 0; + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + static_cast((payload_len >> 24) & 0xFF), + static_cast((payload_len >> 16) & 0xFF), + static_cast((payload_len >> 8) & 0xFF), + static_cast(payload_len & 0xFF), + 0x0B, 0xBA, // 命令码 3002 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + setSocketTimeout(sock, 1000); + + // 发送帧头(带重试) + int total_sent = 0; + int retry = 3; + while (total_sent < 16 && retry-- > 0) { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); + if (ret <= 0) { + usleep(10000); + continue; + } + total_sent += ret; + } + if (total_sent != 16) { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send header failed"; + return; + } + + // 接收响应(先读 16 字节头,再按长度读 JSON) + uint8_t recv_header[16]; + size_t header_read = 0; + while (header_read < 16) { + int ret = recv(sock, recv_header + header_read, 16 - header_read, MSG_NOSIGNAL); + if (ret <= 0) { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "recv header failed or timeout"; + return; + } + header_read += ret; + } + + uint32_t data_len = (recv_header[4] << 24) | + (recv_header[5] << 16) | + (recv_header[6] << 8) | + recv_header[7]; + + std::string json_str; + if (data_len > 0) { + json_str.resize(data_len); + size_t total_read = 0; + while (total_read < data_len) { + int ret = recv(sock, &json_str[total_read], data_len - total_read, MSG_NOSIGNAL); + if (ret <= 0) { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "recv payload failed or timeout"; + return; + } + total_read += ret; + } + } + setSocketTimeout(sock, 0); + + if (json_str.empty()) { + res.ret_code = -4; + res.err_msg = "empty json data"; + return; + } + + // 容错提取 JSON + size_t json_start = json_str.find('{'); + if (json_start == std::string::npos) { + res.ret_code = -4; + res.err_msg = "no JSON found in payload"; + return; + } + size_t json_end = json_str.rfind('}'); + if (json_end != std::string::npos) { + json_str = json_str.substr(json_start, json_end - json_start + 1); + } else { + json_str = json_str.substr(json_start) + "}"; + } + + json root; + std::string parse_err; + if (!parseJson(json_str, root, parse_err)) { + res.ret_code = -3; + res.err_msg = "JSON parse error: " + parse_err; + return; + } + + res.ret_code = root.value("ret_code", 0); + res.create_on = root.value("create_on", ""); + res.err_msg = root.value("err_msg", ""); + + LOG(INFO) << "robotTaskResume success, ret_code=" << res.ret_code; + } + + + + // ============================================================================ + // 3001 - robot_task_pause_req 暂停当前导航任务 (控制端口 19205) + // ============================================================================ + void AgvSrc1100::robotTaskPause(RobotTaskPauseRes& res) { + int sock = sock_nav_; // 使用控制端口 + if (sock < 0) { + res.ret_code = -1; + res.err_msg = "control socket disconnected"; + return; + } + + // 业务负载长度为 0(无 JSON 数据) + uint32_t payload_len = 0; + + // 标准 16 字节帧头(长度 4 字节,命令码 0x0BB9,保留 6 字节) + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x01, + static_cast((payload_len >> 24) & 0xFF), + static_cast((payload_len >> 16) & 0xFF), + static_cast((payload_len >> 8) & 0xFF), + static_cast(payload_len & 0xFF), + 0x0B, 0xB9, // 命令码 3001 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + setSocketTimeout(sock, 1000); // 安全超时 + + // 发送帧头(带重试) + int total_sent = 0; + int retry = 3; + while (total_sent < 16 && retry-- > 0) { + int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); + if (ret <= 0) { + usleep(10000); + continue; + } + total_sent += ret; + } + if (total_sent != 16) { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "send header failed"; + return; + } + + // 接收响应(先读 16 字节帧头,再按长度读 JSON) + uint8_t recv_header[16]; + size_t header_read = 0; + while (header_read < 16) { + int ret = recv(sock, recv_header + header_read, 16 - header_read, MSG_NOSIGNAL); + if (ret <= 0) { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "recv header failed or timeout"; + return; + } + header_read += ret; + } + + // 解析数据区长度(大端 4 字节) + uint32_t data_len = (recv_header[4] << 24) | + (recv_header[5] << 16) | + (recv_header[6] << 8) | + recv_header[7]; + + // 读取数据区(JSON) + std::string json_str; + if (data_len > 0) { + json_str.resize(data_len); + size_t total_read = 0; + while (total_read < data_len) { + int ret = recv(sock, &json_str[total_read], data_len - total_read, MSG_NOSIGNAL); + if (ret <= 0) { + setSocketTimeout(sock, 0); + res.ret_code = -2; + res.err_msg = "recv payload failed or timeout"; + return; + } + total_read += ret; + } + } + setSocketTimeout(sock, 0); + + if (json_str.empty()) { + res.ret_code = -4; + res.err_msg = "empty json data"; + return; + } + + // 容错提取 JSON(可能包含前缀/后缀) + size_t json_start = json_str.find('{'); + if (json_start == std::string::npos) { + res.ret_code = -4; + res.err_msg = "no JSON found in payload"; + return; + } + size_t json_end = json_str.rfind('}'); + if (json_end != std::string::npos) { + json_str = json_str.substr(json_start, json_end - json_start + 1); + } else { + json_str = json_str.substr(json_start) + "}"; + } + + // 解析 JSON + json root; + std::string parse_err; + if (!parseJson(json_str, root, parse_err)) { + res.ret_code = -3; + res.err_msg = "JSON parse error: " + parse_err; + return; + } + + res.ret_code = root.value("ret_code", 0); + res.create_on = root.value("create_on", ""); + res.err_msg = root.value("err_msg", ""); + + LOG(INFO) << "robotTaskPause success, ret_code=" << res.ret_code; + } + + + + + + // -------------------------------------------------------------------------- + // 4010 - robot_config_uploadmap_req 上传地图 + // 功能:将地图文件上传到机器人 + // -------------------------------------------------------------------------- + void AgvSrc1100::uploadMap(UploadMapResult& info, const std::string& map_json) + { + int sock = sock_config_; + if (sock < 0) + { + info.ret_code = -1; + info.err_msg = "socket not connected"; + return; + } + + // 清空残留数据,设置超时 + flushSocket(sock); + setSocketTimeout(sock, 1000); + + // 计算负载长度(地图 JSON 数据) + uint16_t payload_len = static_cast(map_json.size()); + // 命令码 0x0FAA = 4010 + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x00, + 0x00, 0x00, + static_cast((payload_len >> 8) & 0xFF), + static_cast(payload_len & 0xFF), + 0x0F, 0xAA, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + // 发送帧头(带重试) + int totalSent = 0; + int retries = 3; + while (totalSent < 16 && retries-- > 0) + { + int ret = send(sock, send_buf + totalSent, 16 - totalSent, 0); + if (ret <= 0) + { + usleep(10000); + continue; + } + totalSent += ret; + } + if (totalSent != 16) + { + setSocketTimeout(sock, 0); + info.ret_code = -2; + info.err_msg = "send frame header failed"; + return; + } + + // 分片发送地图数据(4096 字节一片) + const int SLICE_SIZE = 4096; + size_t offset = 0; + size_t totalSize = map_json.size(); + const char* data = map_json.c_str(); + + while (offset < totalSize) + { + int sendLen = std::min((size_t)SLICE_SIZE, totalSize - offset); + int ret = send(sock, data + offset, sendLen, 0); + if (ret <= 0) + { + setSocketTimeout(sock, 0); + info.ret_code = -2; + info.err_msg = "send map payload failed"; + return; + } + offset += ret; + usleep(500); // 短暂延时,防止发送过快 + } + + // 接收响应 + const int RECV_BUF_LEN = 65536; + std::vector recvBuf(RECV_BUF_LEN); + std::string rawData; + int recvRet = 0; + while ((recvRet = recv(sock, recvBuf.data(), RECV_BUF_LEN, 0)) > 0) + { + rawData.append(reinterpret_cast(recvBuf.data()), recvRet); + } + setSocketTimeout(sock, 0); + + if (rawData.empty()) + { + info.ret_code = -2; + info.err_msg = "no response data"; + return; + } + + // 提取并解析 JSON + size_t jsonStart = rawData.find('{'); + size_t jsonEnd = rawData.rfind('}'); + std::string json_str; + if (jsonStart == std::string::npos) + { + info.ret_code = -4; + info.err_msg = "json not found"; + return; + } + if (jsonEnd == std::string::npos) + { + json_str = rawData.substr(jsonStart) + "}"; + } + else + { + json_str = rawData.substr(jsonStart, jsonEnd - jsonStart + 1); + } + + json root; + std::string err; + if (!parseJson(json_str, root, err)) + { + info.ret_code = -3; + info.err_msg = "json parse error: " + err; + return; + } + + info.ret_code = root.value("ret_code", 0); + info.create_on = root.value("create_on", ""); + info.err_msg = root.value("err_msg", ""); + } + + + // -------------------------------------------------------------------------- + // 4011 - robot_config_downloadmap_req 下载地图 + // 功能:从机器人下载指定地图文件 + // -------------------------------------------------------------------------- + void AgvSrc1100::downloadMap(DownloadMapResult& info, const std::string& map_name) + { + int sock = sock_config_; + if (sock < 0) + { + info.ret_code = -1; + info.err_msg = "socket not connected"; + return; + } + + // 组装请求 JSON: {"map_name":"xxx"} + std::string req_json = R"({"map_name":")" + map_name + R"("})"; + uint8_t payload_len = static_cast(req_json.size()); + + // 命令码 0x0FAB = 4011 + uint8_t send_buf[16] = { + 0x5A, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, payload_len, + 0x0F, 0xAB, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + flushSocket(sock); + + // 发送帧头 + if (send(sock, send_buf, 16, MSG_NOSIGNAL) != 16) + { + info.ret_code = -2; + info.err_msg = "send frame header failed"; + return; + } + // 发送 JSON 负载 + if (send(sock, req_json.data(), req_json.size(), MSG_NOSIGNAL) != (ssize_t)req_json.size()) + { info.ret_code = -2; info.err_msg = "send payload failed"; return; } - offset += ret; - usleep(500); - } - // 循环读取完整响应数据 - const int recv_buf_len = 65536; - std::vector recv_buf(recv_buf_len); - std::string raw_data; - int recv_ret = 0; - while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) - { - raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); - } - setSocketTimeout(sock, 0); + // 接收地图数据(地图文件可能较大,使用 65536 字节缓冲区) + const int RECV_BUF_LEN = 65536; + std::vector recvBuf(RECV_BUF_LEN); + std::string rawData; + int ret = 0; - // 无响应数据报错 - if (raw_data.empty()) - { - info.ret_code = -2; - info.err_msg = "no response data"; - return; - } - - // 截取完整JSON区间 - size_t json_start = raw_data.find('{'); - size_t json_end = raw_data.rfind('}'); - std::string json_str; - if (json_start == std::string::npos) - { - info.ret_code = -4; - info.err_msg = "json not found in response"; - return; - } - if (json_end == std::string::npos) - { - json_str = raw_data.substr(json_start) + "}"; - } - else - { - json_str = raw_data.substr(json_start, json_end - json_start + 1); - } - - // JSON解析 - json root; - std::string err; - if (!parseJson(json_str, root, err)) - { - info.ret_code = -3; - info.err_msg = "json parse error: " + err; - return; - } - - // 赋值返回字段 - info.ret_code = root.value("ret_code", 0); - info.create_on = root.value("create_on", ""); - info.err_msg = root.value("err_msg", ""); -} - - -// =========================================================================== -// 接口:查询当前控制权所有者 命令码 1060 (0x0424) -// =========================================================================== -/** - * @brief 查询机器人当前抢占控制权信息 - * @param info 输出控制权详情结构体 - */ -void AgvSrc1100::getCurrentLockStatus(CurrentLockStatus& info) -{ - int sock = sock_status_; - if (sock < 0) - { - info.ret_code = -1; - info.err_msg = "socket not connected"; - return; - } - - // 16字节帧头,无JSON负载,负载长度0 - uint16_t payload_len = 0; - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, - static_cast((payload_len >> 8) & 0xFF), - static_cast(payload_len & 0xFF), - 0x04, 0x24, // 命令码 0x0424 = 1060 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - setSocketTimeout(sock, 1000); - - // 循环发送帧头 - int total_sent = 0; - int retry = 3; - while (total_sent < 16 && retry > 0) - { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, 0); - if (ret <= 0) + setSocketTimeout(sock, 500); // 延长超时适配大地图 + while ((ret = recv(sock, recvBuf.data(), RECV_BUF_LEN, 0)) > 0) { - retry--; - usleep(10000); - continue; + rawData.append(reinterpret_cast(recvBuf.data()), ret); } - total_sent += ret; - } - if (total_sent != 16) - { setSocketTimeout(sock, 0); - info.ret_code = -2; - info.err_msg = "send frame header failed"; - return; - } - // 接收完整响应 - const int RECV_BUF_LEN = 65536; - std::vector recv_buf(RECV_BUF_LEN); - std::string raw_data; - int recv_ret = 0; - while ((recv_ret = recv(sock, recv_buf.data(), RECV_BUF_LEN, 0)) > 0) - { - raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); - } - setSocketTimeout(sock, 0); - - if (raw_data.empty()) - { - info.ret_code = -2; - info.err_msg = "no response data"; - return; - } - - // 截取JSON - size_t json_start = raw_data.find('{'); - size_t json_end = raw_data.rfind('}'); - std::string json_str; - if (json_start == std::string::npos) - { - info.ret_code = -4; - info.err_msg = "json not found in response"; - return; - } - if (json_end == std::string::npos) - { - json_str = raw_data.substr(json_start) + "}"; - } - else - { - json_str = raw_data.substr(json_start, json_end - json_start + 1); - } - - // JSON解析 - json root; - std::string err; - if (!parseJson(json_str, root, err)) - { - info.ret_code = -3; - info.err_msg = "json parse error: " + err; - return; - } - - // 字段赋值 - info.locked = root.value("locked", false); - info.ip = root.value("ip", ""); - info.port = root.value("port", 0); - info.type = static_cast(root.value("type", 0)); - info.nick_name = root.value("nick_name", ""); - info.time_t = root.value("time_t", 0LL); - info.desc = root.value("desc", ""); - info.ret_code = root.value("ret_code", 0); - info.create_on = root.value("create_on", ""); - info.err_msg = root.value("err_msg", ""); -} - - - // =========================================================================== -// 接口:开环速度运动控制 命令码 2010 (0x07DA) -// 通信端口:sock_control_ 19205控制端口 -// 下发指令强制取消正在执行的导航任务 -// 多舵轮设备仅解析vx/vy/w;steer、real_steer仅单舵轮设备生效 -// =========================================================================== -void AgvSrc1100::robotMotionControl(MotionCtrlRes& res, const MotionCtrlReq& req) -{ - int sock = sock_control_; - if (sock < 0) - { - res.ret_code = -1; - res.err_msg = "socket not connected"; - return; - } - - json send_json; - if (req.vx != 0.0) send_json["vx"] = req.vx; - if (req.vy != 0.0) send_json["vy"] = req.vy; - if (req.w != 0.0) send_json["w"] = req.w; - if (req.steer != 0.0) send_json["steer"] = req.steer; - if (req.real_steer != 0.0) send_json["real_steer"] = req.real_steer; - if (req.duration >= 0) send_json["duration"] = req.duration; - - std::string payload = send_json.dump(); - uint16_t payload_len = static_cast(payload.size()); - - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, - static_cast((payload_len >> 8) & 0xFF), - static_cast(payload_len & 0xFF), - 0x07, 0xDA, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - setSocketTimeout(sock, 1000); - - int total_sent = 0; - int retry_cnt = 3; - while (total_sent < 16 && retry_cnt > 0) - { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); - if (ret <= 0) + if (rawData.empty()) { - retry_cnt--; - usleep(10000); - continue; - } - total_sent += ret; - } - if (total_sent != 16) - { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send frame header failed"; - return; - } - - const int slice_size = 4096; - size_t offset = 0; - size_t total_size = payload.size(); - const char* data_ptr = payload.data(); - while (offset < total_size) - { - int send_len = std::min((size_t)slice_size, total_size - offset); - int ret = send(sock, data_ptr + offset, send_len, MSG_NOSIGNAL); - if (ret <= 0) - { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send payload failed"; + info.ret_code = -2; + info.err_msg = "no response data"; return; } - offset += ret; - usleep(500); - } - const int recv_buf_len = 65536; - std::vector recv_buf(recv_buf_len); - std::string raw_data; - int recv_ret = 0; - while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) - { - raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); - } - setSocketTimeout(sock, 0); + // 提取并解析 JSON + size_t jsonStart = rawData.find('{'); + size_t jsonEnd = rawData.rfind('}'); + std::string json_str; - if (raw_data.empty()) - { - res.ret_code = -2; - res.err_msg = "no response data"; - return; - } - - size_t json_start = raw_data.find('{'); - size_t json_end = raw_data.rfind('}'); - std::string json_str; - if (json_start == std::string::npos) - { - res.ret_code = -4; - res.err_msg = "json not found in response"; - return; - } - if (json_end == std::string::npos) - { - json_str = raw_data.substr(json_start) + "}"; - } - else - { - json_str = raw_data.substr(json_start, json_end - json_start + 1); - } - - json root; - std::string parse_err; - if (!parseJson(json_str, root, parse_err)) - { - res.ret_code = -3; - res.err_msg = "json parse error: " + parse_err; - return; - } - - res.ret_code = root.value("ret_code", 0); - res.create_on = root.value("create_on", ""); - res.err_msg = root.value("err_msg", ""); -} - - - // =========================================================================== -// 接口:切换载入地图 命令码 2022 (0x07E6) -// 通信端口:sock_control_ 19205控制端口 -// 功能:下发地图名切换设备当前地图,合法名称更新current_map -// =========================================================================== -void AgvSrc1100::robotLoadMap(LoadMapRes& res, const LoadMapReq& req) -{ - - int sock = sock_control_; - if (sock < 0) - { - res.ret_code = -1; - res.err_msg = "socket not connected"; - return; - } - - // 组装请求JSON {"map_name":"xxx"} - json send_json; - send_json["map_name"] = req.map_name; - std::string payload = send_json.dump(); - uint16_t payload_len = static_cast(payload.size()); - - // 16字节协议帧头 0x5A 01 00 01 + 长度大端 + 0x07E6 - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, - static_cast((payload_len >> 8) & 0xFF), - static_cast(payload_len & 0xFF), - 0x07, 0xE6, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - setSocketTimeout(sock, 1000); - - // 重试发送帧头3次 - int total_sent = 0; - int retry_cnt = 3; - while (total_sent < 16 && retry_cnt > 0) - { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); - if (ret <= 0) + if (jsonStart == std::string::npos) { - retry_cnt--; - usleep(10000); - continue; - } - total_sent += ret; - } - if (total_sent != 16) - { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send frame header failed"; - return; - } - - // 分片发送JSON负载 - const int slice_size = 4096; - size_t offset = 0; - size_t total_size = payload.size(); - const char* data_ptr = payload.data(); - while (offset < total_size) - { - int send_len = std::min((size_t)slice_size, total_size - offset); - int ret = send(sock, data_ptr + offset, send_len, MSG_NOSIGNAL); - if (ret <= 0) - { - setSocketTimeout(sock, 3000); - res.ret_code = -2; - res.err_msg = "send payload failed"; + info.ret_code = -4; + info.err_msg = "json not found"; return; } - offset += ret; - usleep(500); - } - - // 接收响应报文 - const int recv_buf_len = 65536; - std::vector recv_buf(recv_buf_len); - std::string raw_data; - int recv_ret = 0; - while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) - { - raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); - } - setSocketTimeout(sock, 0); - - if (raw_data.empty()) - { - res.ret_code = -2; - res.err_msg = "no response data"; - return; - } - - // 截取JSON,兜底补全闭合符 - size_t json_start = raw_data.find('{'); - size_t json_end = raw_data.rfind('}'); - std::string json_str; - if (json_start == std::string::npos) - { - res.ret_code = -4; - res.err_msg = "json not found in response"; - return; - } - if (json_end == std::string::npos) - { - json_str = raw_data.substr(json_start) + "}"; - } - else - { - json_str = raw_data.substr(json_start, json_end - json_start + 1); - } - - // 解析JSON - json root; - std::string parse_err; - if (!parseJson(json_str, root, parse_err)) - { - res.ret_code = -3; - res.err_msg = "json parse error: " + parse_err; - return; - } - - res.ret_code = root.value("ret_code", 0); - res.create_on = root.value("create_on", ""); - res.err_msg = root.value("err_msg", ""); - LOG(INFO) << "recv raw_data len=" << raw_data.size() << " content=" << raw_data; - -} - - // =========================================================================== -// 接口:查询地图载入状态 命令码 1022 (0x03FE) -// 通信端口:sock_status_ 19204状态端口 -// loadmap_status 0=失败 1=成功 2=载入中;载入中禁止重定位 -// =========================================================================== -void AgvSrc1100::queryLoadMapStatus(QueryLoadMapStatusRes& res) -{ - int sock = sock_status_; - if (sock < 0) - { - res.ret_code = -1; - res.err_msg = "socket not connected"; - return; - } - - // 无负载,负载长度0 - uint16_t payload_len = 0; - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, - static_cast((payload_len >> 8) & 0xFF), - static_cast(payload_len & 0xFF), - 0x03, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - setSocketTimeout(sock, 1000); - - int total_sent = 0; - int retry_cnt = 3; - while (total_sent < 16 && retry_cnt > 0) - { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); - if (ret <= 0) + if (jsonEnd == std::string::npos) { - retry_cnt--; - usleep(10000); - continue; + json_str = rawData.substr(jsonStart) + "}"; } - total_sent += ret; - } - if (total_sent != 16) - { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send frame header failed"; - return; - } - - const int recv_buf_len = 65536; - std::vector recv_buf(recv_buf_len); - std::string raw_data; - int recv_ret = 0; - while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) - { - raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); - } - setSocketTimeout(sock, 0); - - if (raw_data.empty()) - { - res.ret_code = -2; - res.err_msg = "no response data"; - return; - } - - size_t json_start = raw_data.find('{'); - size_t json_end = raw_data.rfind('}'); - std::string json_str; - if (json_start == std::string::npos) - { - res.ret_code = -4; - res.err_msg = "json not found in response"; - return; - } - if (json_end == std::string::npos) - { - json_str = raw_data.substr(json_start) + "}"; - } - else - { - json_str = raw_data.substr(json_start, json_end - json_start + 1); - } - - json root; - std::string parse_err; - if (!parseJson(json_str, root, parse_err)) - { - res.ret_code = -3; - res.err_msg = "json parse error: " + parse_err; - return; - } - - res.loadmap_status = root.value("loadmap_status", 0); - res.ret_code = root.value("ret_code", 0); - res.create_on = root.value("create_on", ""); - res.err_msg = root.value("err_msg", ""); -} - - - // =========================================================================== -// 接口:查询当前地图站点列表 命令码 1301 (0x0515) -// 通信端口:sock_status_ 19204状态端口 -// 功能:获取当前加载地图所有LocationMark/ChargePoint/ActionPoint站点 -// =========================================================================== -void AgvSrc1100::queryStationList(QueryStationRes& res) -{ - int sock = sock_status_; - if (sock < 0) - { - res.ret_code = -1; - res.err_msg = "socket not connected"; - return; - } - - uint16_t payload_len = 0; - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, - static_cast((payload_len >> 8) & 0xFF), - static_cast(payload_len & 0xFF), - 0x05, 0x15, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - setSocketTimeout(sock, 1000); - - int total_sent = 0; - int retry_cnt = 3; - while (total_sent < 16 && retry_cnt > 0) - { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); - if (ret <= 0) + else { - retry_cnt--; - usleep(10000); - continue; + json_str = rawData.substr(jsonStart, jsonEnd - jsonStart + 1); } - total_sent += ret; - } - if (total_sent != 16) - { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send frame header failed"; - return; - } - const int recv_buf_len = 65536; - std::vector recv_buf(recv_buf_len); - std::string raw_data; - int recv_ret = 0; - while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) - { - raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); - } - setSocketTimeout(sock, 0); - - if (raw_data.empty()) - { - res.ret_code = -2; - res.err_msg = "no response data"; - return; - } - - size_t json_start = raw_data.find('{'); - size_t json_end = raw_data.rfind('}'); - std::string json_str; - if (json_start == std::string::npos) - { - res.ret_code = -4; - res.err_msg = "json not found in response"; - return; - } - if (json_end == std::string::npos) - { - json_str = raw_data.substr(json_start) + "}"; - } - else - { - json_str = raw_data.substr(json_start, json_end - json_start + 1); - } - - json root; - std::string parse_err; - if (!parseJson(json_str, root, parse_err)) - { - res.ret_code = -3; - res.err_msg = "json parse error: " + parse_err; - return; - } - - res.ret_code = root.value("ret_code", 0); - res.create_on = root.value("create_on", ""); - res.err_msg = root.value("err_msg", ""); - - // 解析站点数组 - // 解析站点数组 - if (root.contains("stations") && root["stations"].is_array()) - { - for (auto& item : root["stations"]) + json root; + std::string err; + if (!parseJson(json_str, root, err)) { - StationItem st; - st.id = item.value("id", ""); - st.type = item.value("type", ""); - st.x = item.value("x", 0.0); - st.y = item.value("y", 0.0); - st.r = item.value("r", 0.0); - st.desc = item.value("desc", ""); - st.executor = item.value("executor", ""); - st.prepoint = item.value("prepoint", ""); - st.recfile = item.value("recfile", ""); - - // 兼容bool/字符串"true"/"false" - if (item.contains("spin")) - { - if (item["spin"].is_boolean()) - st.spin = item["spin"].get(); - else if (item["spin"].is_string()) - st.spin = (item["spin"].get() == "true"); - } - if (item.contains("use_down_pgv")) - { - if (item["use_down_pgv"].is_boolean()) - st.use_down_pgv = item["use_down_pgv"].get(); - else if (item["use_down_pgv"].is_string()) - st.use_down_pgv = (item["use_down_pgv"].get() == "true"); - } - - res.stations.push_back(st); - } - } -} - - -// =========================================================================== -// 接口:指定路径导航 命令码 3066 (0x0BFA) -// 通信端口:sock_control_ 19205控制端口 -// 功能:下发多段连续站点导航任务序列,任务串行排队执行 -// 必填字段:task_id / source_id / id -// =========================================================================== -void AgvSrc1100::robotGoTargetList(GoTargetListRes& res, const GoTargetListReq& req) -{ - int sock = sock_nav_; // ✅ 修正:使用 sock_nav_ 而非 port_nav_ - LOG(INFO) << "robotGoTargetList sock fd=" << sock; - if (sock < 0) - { - res.ret_code = -1; - res.err_msg = "nav socket invalid"; // 更新错误信息 - return; - } - - // 组装请求JSON - json send_json; - json arr = json::array(); - for (const auto& task : req.move_task_list) - { - json obj; - obj["task_id"] = task.task_id; - obj["source_id"] = task.source_id; - obj["id"] = task.id; - if (!task.operation.empty()) - { - obj["operation"] = task.operation; - obj["jack_height"] = task.jack_height; - } - arr.push_back(obj); - } - send_json["move_task_list"] = arr; - std::string payload = send_json.dump(); - uint16_t payload_len = static_cast(payload.size()); - LOG(INFO) << "nav payload length:" << payload_len; - - // 静态帧头缓冲区 - static uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, - 0,0, - 0x0B, 0xFA, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - send_buf[6] = static_cast((payload_len >> 8) & 0xFF); - send_buf[7] = static_cast(payload_len & 0xFF); - - flushSocket(sock); - setSocketTimeout(sock, 3000); - - // 发送16字节帧头 - int total_sent = 0; - int retry_cnt = 5; - while (total_sent < 16 && retry_cnt > 0) - { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); - if (ret <= 0) - { - LOG(ERROR) << "send header failed, ret=" << ret - << ", errno=" << errno - << " (" << strerror(errno) << ")"; - retry_cnt--; - usleep(20000); - continue; // 不再检查 sock_control_,因为 sock 已在开头验证 - } - total_sent += ret; - } - if (total_sent != 16) - { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send frame header failed"; - return; - } - - // 分片发送,缩小分片大小至1024 - const int slice_size = 1024; - size_t offset = 0; - size_t total_size = payload.size(); - const char* data_ptr = payload.data(); - while (offset < total_size) - { - int send_len = std::min((size_t)slice_size, total_size - offset); - int ret = send(sock, data_ptr + offset, send_len, MSG_NOSIGNAL); - if (ret <= 0) - { - LOG(ERROR) << "send payload failed, errno=" << errno << " (" << strerror(errno) << ")"; - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send payload failed"; + info.ret_code = -3; + info.err_msg = err; return; } - offset += ret; + + info.map_name = map_name; + info.ret_code = root.value("ret_code", 0); + info.create_on = root.value("create_on", ""); + info.err_msg = root.value("err_msg", ""); + // 成功时保存地图内容 + info.map_content = (0 == info.ret_code) ? json_str : ""; } - // 接收并解析响应 - const int recv_buf_len = 65536; - std::vector recv_buf(recv_buf_len); - std::string raw_data; - int recv_ret = 0; - while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, MSG_NOSIGNAL)) > 0) - { - raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); - } - setSocketTimeout(sock, 0); - if (raw_data.empty()) - { - res.ret_code = -2; - res.err_msg = "no response data"; - return; - } - - size_t json_start = raw_data.find('{'); - if (json_start == std::string::npos) - { - res.ret_code = -4; - res.err_msg = "json not found in response"; - return; - } - std::string json_str = raw_data.substr(json_start); - if (json_str.back() != '}') - { - json_str += "}"; - } - - json root; - std::string parse_err; - try - { - root = json::parse(json_str); - } - catch (json::parse_error& e) - { - res.ret_code = -3; - res.err_msg = "json parse error: " + std::string(e.what()); - return; - } - - res.ret_code = root.contains("ret_code") ? root["ret_code"].get() : 0; - res.create_on = root.contains("create_on") ? root["create_on"].get() : ""; - res.err_msg = root.contains("err_msg") ? root["err_msg"].get() : ""; -} + // ============================================================================ + // 端口 19210 – 其他 API(允许 5 个连接) + // 功能:外设控制(顶升/货叉/辊筒/音频/IO 等) + // 当前未实现具体方法,预留扩展 + // ============================================================================ - //=========================================================================== -// 1020 robot_status_task_req 0x03FC 查询当前导航状态 sock_status_ 19204 -// 应答编号 11020 0x2B0C -//=========================================================================== -void AgvSrc1100::robotStatusTask(RobotStatusTaskRes& res, const RobotStatusTaskReq& req) -{ - int sock = sock_status_; - LOG(INFO) << "robotStatusTask sock fd=" << sock; - if (sock < 0) - { - res.ret_code = -1; - res.err_msg = "status port socket invalid"; - return; - } - - // 组装请求JSON - json send_json; - if (req.simple) - { - send_json["simple"] = true; - } - std::string payload = send_json.dump(); - uint16_t payload_len = static_cast(payload.size()); - - // 帧头 0x03FC = 1020 - static uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, - 0,0, - 0x03, 0xFC, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - send_buf[6] = static_cast((payload_len >> 8) & 0xFF); - send_buf[7] = static_cast(payload_len & 0xFF); - - flushSocket(sock); - setSocketTimeout(sock, 3000); - - // 发送帧头 5次重试 - int total_sent = 0; - int retry_cnt = 5; - while (total_sent < 16 && retry_cnt > 0) - { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); - if (ret <= 0) - { - retry_cnt--; - usleep(20000); - if (sock_status_ < 0) - { - setSocketTimeout(sock, 0); - res.ret_code = -1; - res.err_msg = "socket broken send header"; - return; - } - continue; - } - total_sent += ret; - } - if (total_sent != 16) - { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send frame header failed"; - return; - } - - // 分片发送payload - const int slice_size = 1024; - size_t offset = 0; - size_t total_size = payload.size(); - const char* data_ptr = payload.data(); - while (offset < total_size) - { - int send_len = std::min((size_t)slice_size, total_size - offset); - int ret = send(sock, data_ptr + offset, send_len, MSG_NOSIGNAL); - if (ret <= 0) - { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send payload failed"; - return; - } - offset += ret; - } - - // 接收应答 - const int recv_buf_len = 65536; - std::vector recv_buf(recv_buf_len); - std::string raw_data; - int recv_ret = 0; - while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, MSG_NOSIGNAL)) > 0) - { - raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); - } - setSocketTimeout(sock, 0); - - if (raw_data.empty()) - { - res.ret_code = -2; - res.err_msg = "no response data"; - return; - } - - // 截取纯JSON - size_t json_start = raw_data.find('{'); - if (json_start == std::string::npos) - { - res.ret_code = -4; - res.err_msg = "json not found in response"; - return; - } - std::string json_str = raw_data.substr(json_start); - if (json_str.back() != '}') - { - json_str += "}"; - } - - // JSON解析捕获异常 - json root; - try - { - root = json::parse(json_str); - } - catch (json::parse_error& e) - { - res.ret_code = -3; - res.err_msg = "json parse error: " + std::string(e.what()); - return; - } - - // 顶层公共字段 - res.ret_code = root.contains("ret_code") ? root["ret_code"].get() : 0; - res.create_on = root.contains("create_on") ? root["create_on"].get() : ""; - res.err_msg = root.contains("err_msg") ? root["err_msg"].get() : ""; - - // 导航基础状态 - res.task_status = root.contains("task_status") ? root["task_status"].get() : 0; - res.task_type = root.contains("task_type") ? root["task_type"].get() : 0; - res.target_id = root.contains("target_id") ? root["target_id"].get() : ""; - res.move_status_info = root.contains("move_status_info") ? root["move_status_info"].get() : ""; - - // target_point x,y,r - if (root.contains("target_point") && root["target_point"].is_array()) - { - for (auto& val : root["target_point"]) - { - res.target_point.push_back(val.get()); - } - } - - // 已走过站点 - if (root.contains("finished_path") && root["finished_path"].is_array()) - { - for (auto& s : root["finished_path"]) - { - res.finished_path.push_back(s.get()); - } - } - // 未走站点 - if (root.contains("unfinished_path") && root["unfinished_path"].is_array()) - { - for (auto& s : root["unfinished_path"]) - { - res.unfinished_path.push_back(s.get()); - } - } - - // 料箱数组 - if (root.contains("containers") && root["containers"].is_array()) - { - for (auto& item : root["containers"]) - { - NavContainerItem c; - c.container_name = item.contains("container_name") ? item["container_name"].get() : ""; - c.desc = item.contains("desc") ? item["desc"].get() : ""; - c.goods_id = item.contains("goods_id") ? item["goods_id"].get() : ""; - if (item.contains("has_goods")) - { - if (item["has_goods"].is_boolean()) - c.has_goods = item["has_goods"].get(); - else if (item["has_goods"].is_string()) - c.has_goods = (item["has_goods"].get() == "true"); - } - res.containers.push_back(c); - } - } -} + // ============================================================================ + // 端口 19301 – 机器人推送 API(允许 10 个连接) + // 功能:接收机器人主动推送的实时状态数据 + // 当前未实现具体方法,预留扩展 + // ============================================================================ -} // namespace cmvr::device - - +} \ No newline at end of file diff --git a/cmvr-es/service/grpc/include/grpc_agv_service.h b/cmvr-es/service/grpc/include/grpc_agv_service.h index d620c474..02253dda 100644 --- a/cmvr-es/service/grpc/include/grpc_agv_service.h +++ b/cmvr-es/service/grpc/include/grpc_agv_service.h @@ -1,6 +1,14 @@ // // Created by xtkuang on 2025/6/1. // +/** + * @file grpc_agv_service.h + * @brief gRPC AGV 服务实现类的头文件。 + * 继承自自动生成的 api::AgvService::Service,提供所有 AGV 相关 RPC 接口的具体实现。 + * 通过 DeviceManager 获取对应的 AGV 设备实例(如 AgvSrc1100),将请求转发至设备层。 + * @author xtkuang + * @date 2025-06-01 + */ #ifndef GRPC_AGV_SERVICE_H #define GRPC_AGV_SERVICE_H @@ -11,121 +19,260 @@ namespace cmvr::service { - class gRPCAGVServiceImpl final : public api::AgvService::Service { - public: - gRPCAGVServiceImpl(); - ~gRPCAGVServiceImpl() override = default; +/** + * @brief gRPC AGV 服务实现类。 + * 负责将 gRPC 请求转换为对具体 AGV 设备的调用,并统一处理异常、日志和时间戳。 + */ +class gRPCAGVServiceImpl final : public api::AgvService::Service { +public: + /** + * @brief 构造函数,获取 DeviceManager 单例引用。 + */ + gRPCAGVServiceImpl(); - // AGV服务接口 - grpc::Status GetStatusInfo(grpc::ServerContext* context, - const api::GetAgvStatusInfoCommand_Request* request, - api::GetAgvStatusInfoCommand_Feedback* response) override; + /** + * @brief 默认析构函数。 + */ + ~gRPCAGVServiceImpl() override = default; - grpc::Status GetBatteryStatus(grpc::ServerContext* context, - const api::RobotStatusBatteryCommand_Request* request, - api::RobotStatusBatteryCommand_Feedback* response) override; + // ===================== 基本状态查询接口 ===================== - grpc::Status GetRobotLocation(grpc::ServerContext* context, - const api::RobotStatusLocCommand_Request* request, - api::RobotStatusLocCommand_Feedback* response) override; + /** + * @brief 获取 AGV 基本信息(命令码 1000)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID(header.device_id)。 + * @param response 响应消息,填充 AGV 状态信息(版本、型号、IP、MAC 等)。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status GetStatusInfo(grpc::ServerContext* context, + const api::GetAgvStatusInfoCommand_Request* request, + api::GetAgvStatusInfoCommand_Feedback* response) override; - grpc::Status RobotConfigDownloadMap(grpc::ServerContext* context, - const api::RobotConfigDownloadMapCommand_Request* request, - api::RobotConfigDownloadMapCommand_Feedback* response) override; + /** + * @brief 查询电池状态(命令码 1007)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID 和可选的 simple 标志。 + * @param response 响应消息,填充电池电量、温度、充放电状态等。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status GetBatteryStatus(grpc::ServerContext* context, + const api::RobotStatusBatteryCommand_Request* request, + api::RobotStatusBatteryCommand_Feedback* response) override; - grpc::Status GetMapStatus(grpc::ServerContext* context, - const api::RobotStatusMapCommand_Request* request, - api::RobotStatusMapCommand_Feedback* response) override; + /** + * @brief 查询机器人当前位置(命令码 1004)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID。 + * @param response 响应消息,填充坐标、朝向角、置信度、当前站点等。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status GetRobotLocation(grpc::ServerContext* context, + const api::RobotStatusLocCommand_Request* request, + api::RobotStatusLocCommand_Feedback* response) override; - grpc::Status RobotConfigUploadMap(grpc::ServerContext* context, - const api::RobotConfigUploadMapCommand_Request* request, - api::RobotConfigUploadMapCommand_Feedback* response) override; - /** - * @brief gRPC接口:抢占机器人控制权 - */ - grpc::Status RobotConfigLock(grpc::ServerContext* context, - const api::RobotConfigLockCommand_Request* request, - api::RobotConfigLockCommand_Feedback* response) override; + // ===================== 地图管理接口 ===================== + /** + * @brief 下载指定地图(命令码 4011)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID 和地图名称。 + * @param response 响应消息,返回地图 JSON 内容或错误码。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status RobotConfigDownloadMap(grpc::ServerContext* context, + const api::RobotConfigDownloadMapCommand_Request* request, + api::RobotConfigDownloadMapCommand_Feedback* response) override; + /** + * @brief 查询地图状态(命令码 1300)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID。 + * @param response 响应消息,返回当前地图名称、所有地图列表及文件详情。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status GetMapStatus(grpc::ServerContext* context, + const api::RobotStatusMapCommand_Request* request, + api::RobotStatusMapCommand_Feedback* response) override; - /** - * @brief gRPC接口 查询当前控制权持有者 - */ - grpc::Status GetCurrentLockStatus(grpc::ServerContext* context, - const api::RobotStatusCurrentLockCommand_Request* request, - api::RobotStatusCurrentLockCommand_Feedback* response) override; + /** + * @brief 上传地图(命令码 4010)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID 和完整的地图 JSON 字符串。 + * @param response 响应消息,返回上传结果(ret_code)。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status RobotConfigUploadMap(grpc::ServerContext* context, + const api::RobotConfigUploadMapCommand_Request* request, + api::RobotConfigUploadMapCommand_Feedback* response) override; - /** - * @brief AGV开环速度运动控制 - * @param context grpc上下文 - * @param request 请求体,header携带device_id区分多AGV - * @param response 运动指令执行结果 - */ - grpc::Status RobotMotionControl(grpc::ServerContext* context, - const api::RobotMotionControlCommand_Request* request, - api::RobotMotionControlCommand_Feedback* response) override; + // ===================== 控制权管理接口 ===================== + /** + * @brief 抢占机器人控制权(命令码 4005)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID 和抢占者昵称。 + * @param response 响应消息,返回抢占结果(ret_code)。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status RobotConfigLock(grpc::ServerContext* context, + const api::RobotConfigLockCommand_Request* request, + api::RobotConfigLockCommand_Feedback* response) override; - /** - * @brief 切换载入地图 - * @param context grpc上下文 - * @param request 请求体,携带目标地图名称 - * @param response 切换地图执行结果 - */ - grpc::Status RobotLoadMap( - grpc::ServerContext* context, - const api::RobotLoadMapCommand_Request* request, - api::RobotLoadMapCommand_Feedback* response) override; + /** + * @brief 查询当前控制权持有者(命令码 1060)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID。 + * @param response 响应消息,返回是否被锁定、持有者 IP/端口/昵称等信息。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status GetCurrentLockStatus(grpc::ServerContext* context, + const api::RobotStatusCurrentLockCommand_Request* request, + api::RobotStatusCurrentLockCommand_Feedback* response) override; - /** - * @brief 查询地图载入状态 - * @param context grpc上下文 - * @param request 请求头 - * @param response 地图加载状态结果 - */ - grpc::Status QueryLoadMapStatus( - grpc::ServerContext* context, - const api::RobotQueryLoadMapStatusCommand_Request* request, - api::RobotQueryLoadMapStatusCommand_Feedback* response) override; + // ===================== 运动控制接口 ===================== - /** - * @brief 查询当前地图所有站点信息 - * @param context grpc上下文 - * @param request 请求头 - * @param response 站点列表结果 - */ - grpc::Status QueryStationList( - grpc::ServerContext* context, - const api::QueryStationListCommand_Request* request, - api::QueryStationListCommand_Feedback* response) override; + /** + * @brief 下发开环速度运动指令(命令码 2010)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID 和速度参数(vx, vy, w, steer, duration 等)。 + * @param response 响应消息,返回指令下发结果(ret_code)。 + * @note 此指令会强制取消当前自动导航任务,多舵轮设备仅 vx/vy/w 生效。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status RobotMotionControl(grpc::ServerContext* context, + const api::RobotMotionControlCommand_Request* request, + api::RobotMotionControlCommand_Feedback* response) override; + /** + * @brief 切换载入地图(命令码 2022)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID 和目标地图名称。 + * @param response 响应消息,返回切换结果(ret_code)。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status RobotLoadMap(grpc::ServerContext* context, + const api::RobotLoadMapCommand_Request* request, + api::RobotLoadMapCommand_Feedback* response) override; - /** - * @brief 指定路径连续站点导航 - * @param context grpc上下文 - * @param request 导航任务列表请求 - * @param response 下发任务返回结果 - */ - grpc::Status RobotGoTargetList( - grpc::ServerContext* context, - const api::RobotGoTargetListCommand_Request* request, - api::RobotGoTargetListCommand_Feedback* response) override; + /** + * @brief 停止开环运动(命令码 2000)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID(无业务数据)。 + * @param response 响应消息,返回停止结果(ret_code)。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status RobotControlStop(grpc::ServerContext* context, + const api::RobotControlStopCommand::Request* request, + api::RobotControlStopCommand::Feedback* response) override; + // ===================== 导航任务接口 ===================== - grpc::Status RobotStatusTask( - grpc::ServerContext* context, - const api::RobotStatusTaskCommand_Request* request, - api::RobotStatusTaskCommand_Feedback* response) override; + /** + * @brief 查询地图载入状态(命令码 1022)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID。 + * @param response 响应消息,返回 loadmap_status(0=失败, 1=成功, 2=载入中)。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status QueryLoadMapStatus(grpc::ServerContext* context, + const api::RobotQueryLoadMapStatusCommand_Request* request, + api::RobotQueryLoadMapStatusCommand_Feedback* response) override; + /** + * @brief 查询当前地图所有站点信息(命令码 1301)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID。 + * @param response 响应消息,返回站点列表(ID、坐标、类型等)。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status QueryStationList(grpc::ServerContext* context, + const api::QueryStationListCommand_Request* request, + api::QueryStationListCommand_Feedback* response) override; + /** + * @brief 指定路径导航(命令码 3066)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID 和多段导航任务列表(move_task_list)。 + * @param response 响应消息,返回下发结果(ret_code=0 仅表示接收成功,不表示执行完成)。 + * @attention 每个任务必须包含 task_id, source_id, id,且相邻站点间必须有直接路径。 + * 任务会排队执行,适合多车调度场景。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status RobotGoTargetList(grpc::ServerContext* context, + const api::RobotGoTargetListCommand_Request* request, + api::RobotGoTargetListCommand_Feedback* response) override; + /** + * @brief 查询当前实时导航状态(命令码 1020)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID 和 simple 标志。 + * @param response 响应消息,返回任务状态、任务类型、目标、路径等信息。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status RobotStatusTaskCurrent(grpc::ServerContext* context, + const api::RobotStatusTaskCurrentCommand_Request* request, + api::RobotStatusTaskCurrentCommand_Feedback* response) override; + /** + * @brief 批量查询任务状态(命令码 1110)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID 和 task_ids 列表(为空则查询所有未完成+最近完成)。 + * @param response 响应消息,返回任务状态包(进度、距离、各任务状态等)。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status RobotStatusTaskPackage(grpc::ServerContext* context, + const api::RobotStatusTaskPackageCommand_Request* request, + api::RobotStatusTaskPackageCommand_Feedback* response) override; + /** + * @brief 单点站点自动规划导航(命令码 3051)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID 和完整的导航参数(支持自由导航、动作、PGV 等)。 + * @param response 响应消息,返回下发结果(ret_code=0 仅表示接收成功)。 + * @warning 严禁用于多车调度场景,仅限单车测试;新任务会取消当前任务(不排队)。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status RobotGoTarget(grpc::ServerContext* context, + const api::RobotGoTargetCommand_Request* request, + api::RobotGoTargetCommand_Feedback* response) override; - private: - device::DeviceManager& dmgr_; - }; -} + /** + * @brief 暂停当前导航任务(命令码 3001)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID(无业务数据)。 + * @param response 响应消息,返回暂停结果(ret_code)。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status RobotTaskPause(grpc::ServerContext* context, + const api::RobotTaskPauseCommand::Request* request, + api::RobotTaskPauseCommand::Feedback* response) override; -#endif //GRPC_AGV_SERVICE_H \ No newline at end of file + /** + * @brief 继续当前导航任务(命令码 3002)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID(无业务数据)。 + * @param response 响应消息,返回继续结果(ret_code)。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status RobotTaskResume(grpc::ServerContext* context, + const api::RobotTaskResumeCommand::Request* request, + api::RobotTaskResumeCommand::Feedback* response) override; + + /** + * @brief 取消当前导航任务(命令码 3003)。 + * @param context gRPC 上下文(未使用)。 + * @param request 请求消息,包含设备 ID(无业务数据)。 + * @param response 响应消息,返回取消结果(ret_code)。 + * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 + */ + grpc::Status RobotTaskCancel(grpc::ServerContext* context, + const api::RobotTaskCancelCommand::Request* request, + api::RobotTaskCancelCommand::Feedback* response) override; + +private: + device::DeviceManager& dmgr_; ///< 设备管理器引用,用于根据 device_id 获取 AGV 设备实例。 +}; + +} // namespace cmvr::service + +#endif // GRPC_AGV_SERVICE_H \ No newline at end of file diff --git a/cmvr-es/service/grpc/src/grpc_agv_service.cpp b/cmvr-es/service/grpc/src/grpc_agv_service.cpp index 8b7fd741..a48e03b2 100644 --- a/cmvr-es/service/grpc/src/grpc_agv_service.cpp +++ b/cmvr-es/service/grpc/src/grpc_agv_service.cpp @@ -1,6 +1,15 @@ // // Created by xtkuang on 2025/6/1. // +/** + * @file grpc_agv_service.cpp + * @brief gRPC AGV 服务实现。 + * 每个 RPC 方法从请求头中提取 device_id,通过 DeviceManager 获取对应的 AGV 设备, + * 调用设备层的具体方法,并将结果转换为 Protobuf 响应。 + * 统一处理异常,设置响应头中的 success/error_message 和时间戳。 + * @author xtkuang + * @date 2025-06-01 + */ #include "./service/grpc/include/grpc_agv_service.h" @@ -12,9 +21,21 @@ namespace cmvr::service { + /** + * @brief 构造函数,获取 DeviceManager 单例。 + */ gRPCAGVServiceImpl::gRPCAGVServiceImpl() : dmgr_(device::DeviceManager::getInstance()) { } + // ===================== GetStatusInfo ===================== + /** + * @brief 获取 AGV 基本信息。 + * @details 从请求中提取 device_id,调用设备层的 getStatusInfo,填充响应。 + * @param context 未使用。 + * @param request 包含设备 ID。 + * @param response 返回状态信息和响应头。 + * @return 始终返回 OK,错误通过 response.header 传递。 + */ grpc::Status gRPCAGVServiceImpl::GetStatusInfo( grpc::ServerContext* context, const api::GetAgvStatusInfoCommand_Request* request, @@ -28,6 +49,7 @@ namespace cmvr::service { device::AbstractAgv::AgvStatusInfo device_status; agv_device->getStatusInfo(device_status); + // 映射到 Protobuf 消息 auto* proto_status = response->mutable_status(); proto_status->set_id(device_status.id); proto_status->set_vehicle_id(device_status.vehicle_id); @@ -58,62 +80,75 @@ namespace cmvr::service { } } - -grpc::Status gRPCAGVServiceImpl::GetBatteryStatus( -grpc::ServerContext* context, -const api::RobotStatusBatteryCommand_Request* request, -api::RobotStatusBatteryCommand_Feedback* response) -{ - try { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (GetBatteryStatus): id=" << dev_id; - const auto agv_device = dmgr_.getDevice(dev_id); - - bool simple = false; - if (request->has_data()) - { - simple = request->data().simple(); - } - - device::AbstractAgv::BatteryStatus bat_info; - agv_device->getBatteryStatus(bat_info, simple); - - auto* proto_bat = response->mutable_status(); - proto_bat->set_battery_level(bat_info.battery_level); - proto_bat->set_battery_temp(bat_info.battery_temp); - proto_bat->set_charging(bat_info.charging); - proto_bat->set_voltage(bat_info.voltage); - proto_bat->set_current(bat_info.current); - proto_bat->set_max_charge_voltage(bat_info.max_charge_voltage); - proto_bat->set_max_charge_current(bat_info.max_charge_current); - proto_bat->set_manual_charge(bat_info.manual_charge); - proto_bat->set_auto_charge(bat_info.auto_charge); - proto_bat->set_battery_cycle(bat_info.battery_cycle); - proto_bat->set_battery_user_data(bat_info.battery_user_data); - proto_bat->set_extra(bat_info.extra); - proto_bat->set_ret_code(bat_info.ret_code); - proto_bat->set_create_on(bat_info.create_on); - proto_bat->set_err_msg(bat_info.err_msg); - - auto* header = response->mutable_header(); - header->set_success(true); - header->set_error_message(""); - setCurrentTimestamp(header->mutable_timestamp()); - - LOG(INFO) << "GetBatteryStatus device_id:" << dev_id << " level: " << bat_info.battery_level; - return grpc::Status::OK; - } - catch (std::exception &e) + // ===================== GetBatteryStatus ===================== + /** + * @brief 查询电池状态。 + * @details 支持 simple 参数,调用设备层 getBatteryStatus。 + * @param context 未使用。 + * @param request 包含设备 ID 和 simple 标志。 + * @param response 返回电池信息。 + */ + grpc::Status gRPCAGVServiceImpl::GetBatteryStatus( + grpc::ServerContext* context, + const api::RobotStatusBatteryCommand_Request* request, + api::RobotStatusBatteryCommand_Feedback* response) { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message(e.what()); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; + try { + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (GetBatteryStatus): id=" << dev_id; + const auto agv_device = dmgr_.getDevice(dev_id); + + bool simple = false; + if (request->has_data()) + { + simple = request->data().simple(); + } + + device::AbstractAgv::BatteryStatus bat_info; + agv_device->getBatteryStatus(bat_info, simple); + + auto* proto_bat = response->mutable_status(); + proto_bat->set_battery_level(bat_info.battery_level); + proto_bat->set_battery_temp(bat_info.battery_temp); + proto_bat->set_charging(bat_info.charging); + proto_bat->set_voltage(bat_info.voltage); + proto_bat->set_current(bat_info.current); + proto_bat->set_max_charge_voltage(bat_info.max_charge_voltage); + proto_bat->set_max_charge_current(bat_info.max_charge_current); + proto_bat->set_manual_charge(bat_info.manual_charge); + proto_bat->set_auto_charge(bat_info.auto_charge); + proto_bat->set_battery_cycle(bat_info.battery_cycle); + proto_bat->set_battery_user_data(bat_info.battery_user_data); + proto_bat->set_extra(bat_info.extra); + proto_bat->set_ret_code(bat_info.ret_code); + proto_bat->set_create_on(bat_info.create_on); + proto_bat->set_err_msg(bat_info.err_msg); + + auto* header = response->mutable_header(); + header->set_success(true); + header->set_error_message(""); + setCurrentTimestamp(header->mutable_timestamp()); + + LOG(INFO) << "GetBatteryStatus device_id:" << dev_id << " level: " << bat_info.battery_level; + return grpc::Status::OK; + } + catch (std::exception &e) + { + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message(e.what()); + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } } -} - + // ===================== GetRobotLocation ===================== + /** + * @brief 查询机器人当前位置。 + * @param context 未使用。 + * @param request 包含设备 ID。 + * @param response 返回坐标、朝向、置信度等。 + */ grpc::Status gRPCAGVServiceImpl::GetRobotLocation( grpc::ServerContext* context, const api::RobotStatusLocCommand_Request* request, @@ -157,7 +192,13 @@ api::RobotStatusBatteryCommand_Feedback* response) } } - + // ===================== RobotConfigDownloadMap ===================== + /** + * @brief 下载指定地图。 + * @param context 未使用。 + * @param request 包含设备 ID 和地图名称。 + * @param response 返回地图 JSON 内容或错误码。 + */ grpc::Status gRPCAGVServiceImpl::RobotConfigDownloadMap( grpc::ServerContext* context, const api::RobotConfigDownloadMapCommand_Request* request, @@ -196,8 +237,13 @@ api::RobotStatusBatteryCommand_Feedback* response) } } - - + // ===================== GetMapStatus ===================== + /** + * @brief 查询地图状态。 + * @param context 未使用。 + * @param request 包含设备 ID。 + * @param response 返回当前地图、地图列表及文件详情。 + */ grpc::Status gRPCAGVServiceImpl::GetMapStatus( grpc::ServerContext* context, const api::RobotStatusMapCommand_Request* request, @@ -249,7 +295,13 @@ api::RobotStatusBatteryCommand_Feedback* response) } } - + // ===================== RobotConfigUploadMap ===================== + /** + * @brief 上传地图。 + * @param context 未使用。 + * @param request 包含设备 ID 和地图 JSON 字符串。 + * @param response 返回上传结果。 + */ grpc::Status gRPCAGVServiceImpl::RobotConfigUploadMap( grpc::ServerContext* context, const api::RobotConfigUploadMapCommand_Request* request, @@ -287,39 +339,33 @@ api::RobotStatusBatteryCommand_Feedback* response) } } - + // ===================== RobotConfigLock ===================== /** - * @brief gRPC 抢占机器人控制权接口 - * @param context grpc上下文 - * @param request 前端请求参数,携带nick_name抢占者名称 - * @param response 抢占结果返回体 - */ + * @brief 抢占机器人控制权。 + * @param context 未使用。 + * @param request 包含设备 ID 和抢占者昵称。 + * @param response 返回抢占结果。 + */ grpc::Status gRPCAGVServiceImpl::RobotConfigLock( grpc::ServerContext* context, const api::RobotConfigLockCommand_Request* request, api::RobotConfigLockCommand_Feedback* response) { try { - // 动态从header拿设备ID,和查询接口统一 std::string dev_id = request->header().device_id(); LOG(INFO) << "[gRPCAGVServiceImpl] (RobotConfigLock): id=" << dev_id; - // 根据device_id获取对应AGV实例 const auto agv_device = dmgr_.getDevice(dev_id); - // 读取抢占者名称 std::string nick_name = request->data().nick_name(); device::AbstractAgv::LockResult lock_res; - // 调用底层抢占接口 agv_device->lockRobotControl(lock_res, nick_name); - // 填充protobuf返回数据 auto* proto_status = response->mutable_status(); proto_status->set_ret_code(lock_res.ret_code); proto_status->set_create_on(lock_res.create_on); proto_status->set_err_msg(lock_res.err_msg); - // 响应头标记成功 + 时间戳对齐全局规范 auto* header = response->mutable_header(); header->set_success(true); header->set_error_message(""); @@ -330,7 +376,6 @@ api::RobotStatusBatteryCommand_Feedback* response) } catch (std::exception &e) { - // 统一异常捕获,和相机/查询接口风格完全一致 auto* header = response->mutable_header(); header->set_success(false); header->set_error_message(e.what()); @@ -339,71 +384,72 @@ api::RobotStatusBatteryCommand_Feedback* response) } } - -/** - * @brief 查询机器人当前控制权信息gRPC接口 - */ -grpc::Status gRPCAGVServiceImpl::GetCurrentLockStatus( + // ===================== GetCurrentLockStatus ===================== + /** + * @brief 查询当前控制权持有者。 + * @param context 未使用。 + * @param request 包含设备 ID。 + * @param response 返回锁定状态、持有者信息等。 + */ + grpc::Status gRPCAGVServiceImpl::GetCurrentLockStatus( grpc::ServerContext* context, const api::RobotStatusCurrentLockCommand_Request* request, api::RobotStatusCurrentLockCommand_Feedback* response) -{ - try { - // 1. 读取请求device_id,打印日志(和相机代码保持一致) - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (GetCurrentLockStatus): id=" << dev_id; - - // 2. 按device_id获取AGV设备实例,和相机 dmgr_.getDevice(dev_id) 统一写法 - const auto agv_dev = dmgr_.getDevice(dev_id); - - // 3. 调用底层接口获取控制权信息 - device::AbstractAgv::CurrentLockStatus lock_info; - agv_dev->getCurrentLockStatus(lock_info); - - // 4. 填充protobuf返回结构体 - auto* proto_status = response->mutable_status(); - proto_status->set_locked(lock_info.locked); - proto_status->set_ip(lock_info.ip); - proto_status->set_port(lock_info.port); - proto_status->set_type(lock_info.type); - proto_status->set_nick_name(lock_info.nick_name); - proto_status->set_time_t(lock_info.time_t); - proto_status->set_desc(lock_info.desc); - proto_status->set_ret_code(lock_info.ret_code); - proto_status->set_create_on(lock_info.create_on); - proto_status->set_err_msg(lock_info.err_msg); - - // 5. 响应header标记成功 + 填充时间戳(对齐相机接口) - response->mutable_header()->set_success(true); - setCurrentTimestamp(response->mutable_header()->mutable_timestamp()); - - LOG(INFO) << "Query current lock status, device_id:" << dev_id - << ", locked:" << lock_info.locked << ", nick:" << lock_info.nick_name; - return grpc::Status::OK; - } - catch (std::exception &e) { - // 异常统一捕获,失败header,填错误信息+时间戳,和相机完全对齐 - response->mutable_header()->set_success(false); - response->mutable_header()->set_error_message(e.what()); - setCurrentTimestamp(response->mutable_header()->mutable_timestamp()); - return grpc::Status::OK; + try { + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (GetCurrentLockStatus): id=" << dev_id; + + const auto agv_dev = dmgr_.getDevice(dev_id); + + device::AbstractAgv::CurrentLockStatus lock_info; + agv_dev->getCurrentLockStatus(lock_info); + + auto* proto_status = response->mutable_status(); + proto_status->set_locked(lock_info.locked); + proto_status->set_ip(lock_info.ip); + proto_status->set_port(lock_info.port); + proto_status->set_type(lock_info.type); + proto_status->set_nick_name(lock_info.nick_name); + proto_status->set_time_t(lock_info.time_t); + proto_status->set_desc(lock_info.desc); + proto_status->set_ret_code(lock_info.ret_code); + proto_status->set_create_on(lock_info.create_on); + proto_status->set_err_msg(lock_info.err_msg); + + response->mutable_header()->set_success(true); + setCurrentTimestamp(response->mutable_header()->mutable_timestamp()); + + LOG(INFO) << "Query current lock status, device_id:" << dev_id + << ", locked:" << lock_info.locked << ", nick:" << lock_info.nick_name; + return grpc::Status::OK; + } + catch (std::exception &e) + { + response->mutable_header()->set_success(false); + response->mutable_header()->set_error_message(e.what()); + setCurrentTimestamp(response->mutable_header()->mutable_timestamp()); + return grpc::Status::OK; + } } -} - + // ===================== RobotMotionControl ===================== + /** + * @brief 开环速度运动控制。 + * @param context 未使用。 + * @param request 包含设备 ID 和速度参数。 + * @param response 返回指令下发结果。 + */ grpc::Status gRPCAGVServiceImpl::RobotMotionControl( grpc::ServerContext* context, const api::RobotMotionControlCommand_Request* request, api::RobotMotionControlCommand_Feedback* response) { try { - // 从header读取device_id,区分多台AGV设备 std::string dev_id = request->header().device_id(); LOG(INFO) << "[gRPCAGVServiceImpl] (RobotMotionControl): target device_id=" << dev_id; const auto agv_dev = dmgr_.getDevice(dev_id); - // grpc入参转换到底层结构体 device::AbstractAgv::MotionCtrlReq req; auto& input_data = request->data(); req.vx = input_data.vx(); @@ -413,17 +459,14 @@ grpc::Status gRPCAGVServiceImpl::GetCurrentLockStatus( req.real_steer = input_data.real_steer(); req.duration = input_data.duration(); - // 调用底层运动控制接口 device::AbstractAgv::MotionCtrlRes res; agv_dev->robotMotionControl(res, req); - // 填充grpc返回状态 auto* resp_status = response->mutable_status(); resp_status->set_ret_code(res.ret_code); resp_status->set_create_on(res.create_on); resp_status->set_err_msg(res.err_msg); - // 统一规范header:成功标记+时间戳 auto* header = response->mutable_header(); header->set_success(true); header->set_error_message(""); @@ -436,7 +479,6 @@ grpc::Status gRPCAGVServiceImpl::GetCurrentLockStatus( } catch (std::exception& e) { - // 全局统一异常捕获模板 auto* header = response->mutable_header(); header->set_success(false); header->set_error_message(e.what()); @@ -445,6 +487,13 @@ grpc::Status gRPCAGVServiceImpl::GetCurrentLockStatus( } } + // ===================== RobotLoadMap ===================== + /** + * @brief 切换载入地图。 + * @param context 未使用。 + * @param request 包含设备 ID 和目标地图名称。 + * @param response 返回切换结果。 + */ grpc::Status gRPCAGVServiceImpl::RobotLoadMap( grpc::ServerContext* context, const api::RobotLoadMapCommand_Request* request, @@ -470,7 +519,6 @@ grpc::Status gRPCAGVServiceImpl::GetCurrentLockStatus( auto* header = response->mutable_header(); header->set_success(true); header->set_error_message(""); - // 修复:取timestamp子字段 setCurrentTimestamp(header->mutable_timestamp()); LOG(INFO) << "RobotLoadMap finish, dev_id:" << dev_id @@ -483,13 +531,18 @@ grpc::Status gRPCAGVServiceImpl::GetCurrentLockStatus( auto* header = response->mutable_header(); header->set_success(false); header->set_error_message(e.what()); - // 修复:取timestamp子字段 setCurrentTimestamp(header->mutable_timestamp()); return grpc::Status::OK; } } - + // ===================== QueryLoadMapStatus ===================== + /** + * @brief 查询地图载入状态。 + * @param context 未使用。 + * @param request 包含设备 ID。 + * @param response 返回 loadmap_status。 + */ grpc::Status gRPCAGVServiceImpl::QueryLoadMapStatus( grpc::ServerContext* context, const api::RobotQueryLoadMapStatusCommand_Request* request, @@ -529,6 +582,13 @@ grpc::Status gRPCAGVServiceImpl::GetCurrentLockStatus( } } + // ===================== QueryStationList ===================== + /** + * @brief 查询当前地图所有站点信息。 + * @param context 未使用。 + * @param request 包含设备 ID。 + * @param response 返回站点列表。 + */ grpc::Status gRPCAGVServiceImpl::QueryStationList( grpc::ServerContext* context, const api::QueryStationListCommand_Request* request, @@ -547,7 +607,6 @@ grpc::Status gRPCAGVServiceImpl::GetCurrentLockStatus( resp_status->set_create_on(res.create_on); resp_status->set_err_msg(res.err_msg); - // 填充站点数组 for (auto& st : res.stations) { auto* pb_st = resp_status->add_stations(); @@ -584,7 +643,13 @@ grpc::Status gRPCAGVServiceImpl::GetCurrentLockStatus( } } - + // ===================== RobotGoTargetList ===================== + /** + * @brief 指定路径导航(多点)。 + * @param context 未使用。 + * @param request 包含设备 ID 和 move_task_list。 + * @param response 返回下发结果(仅表示指令被接收)。 + */ grpc::Status gRPCAGVServiceImpl::RobotGoTargetList( grpc::ServerContext* context, const api::RobotGoTargetListCommand_Request* request, @@ -636,28 +701,31 @@ grpc::Status gRPCAGVServiceImpl::GetCurrentLockStatus( } } - - - - grpc::Status gRPCAGVServiceImpl::RobotStatusTask( + // ===================== RobotStatusTaskCurrent ===================== + /** + * @brief 查询当前实时导航状态。 + * @param context 未使用。 + * @param request 包含设备 ID 和 simple 标志。 + * @param response 返回任务状态、类型、目标、路径等。 + */ + grpc::Status gRPCAGVServiceImpl::RobotStatusTaskCurrent( grpc::ServerContext* context, - const api::RobotStatusTaskCommand_Request* request, - api::RobotStatusTaskCommand_Feedback* response) + const api::RobotStatusTaskCurrentCommand_Request* request, + api::RobotStatusTaskCurrentCommand_Feedback* response) { try { std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (RobotStatusTask): id=" << dev_id; + LOG(INFO) << "[gRPCAGVServiceImpl] (RobotStatusTaskCurrent): id=" << dev_id; const auto agv_dev = dmgr_.getDevice(dev_id); - // 嵌套结构体 必须加 AbstractAgv:: - device::AbstractAgv::RobotStatusTaskReq req; + device::AbstractAgv::RobotStatusTaskCurrentReq req; if (request->has_data()) { req.simple = request->data().simple(); } - device::AbstractAgv::RobotStatusTaskRes res; - agv_dev->robotStatusTask(res, req); + device::AbstractAgv::RobotStatusTaskCurrentRes res; + agv_dev->robotStatusTaskCurrent(res, req); auto* out_data = response->mutable_data(); out_data->set_ret_code(res.ret_code); @@ -690,7 +758,7 @@ grpc::Status gRPCAGVServiceImpl::GetCurrentLockStatus( header->set_error_message(""); setCurrentTimestamp(header->mutable_timestamp()); - LOG(INFO) << "RobotStatusTask device_id:" << dev_id << " task_status: " << res.task_status; + LOG(INFO) << "RobotStatusTaskCurrent dev_id:" << dev_id << " task_status:" << res.task_status; return grpc::Status::OK; } catch (std::exception &e) @@ -703,6 +771,438 @@ grpc::Status gRPCAGVServiceImpl::GetCurrentLockStatus( } } + // ===================== RobotStatusTaskPackage ===================== + /** + * @brief 批量查询任务状态。 + * @param context 未使用。 + * @param request 包含设备 ID 和 task_ids 列表。 + * @param response 返回任务状态包(进度、距离、每个任务状态等)。 + */ + grpc::Status gRPCAGVServiceImpl::RobotStatusTaskPackage( + grpc::ServerContext* context, + const api::RobotStatusTaskPackageCommand_Request* request, + api::RobotStatusTaskPackageCommand_Feedback* response) + { + try { + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[gRPCAGVServiceImpl] (RobotStatusTaskPackage): id=" << dev_id; + const auto agv_dev = dmgr_.getDevice(dev_id); + device::AbstractAgv::QueryTaskStatusPackageReq req; + const auto& pb_ids = request->data().task_ids(); + for (auto& tid : pb_ids) + { + req.task_ids.push_back(tid); + } -} \ No newline at end of file + device::AbstractAgv::QueryTaskStatusPackageRes res; + agv_dev->robotStatusTaskPackage(res, req); + + auto* out_data = response->mutable_data(); + out_data->set_ret_code(res.ret_code); + out_data->set_create_on(res.create_on); + out_data->set_err_msg(res.err_msg); + + auto* pkg_pb = out_data->mutable_task_status_package(); + auto& pkg_data = res.task_status_package; + pkg_pb->set_closest_target(pkg_data.closest_target); + pkg_pb->set_source_name(pkg_data.source_name); + pkg_pb->set_target_name(pkg_data.target_name); + pkg_pb->set_percentage(pkg_data.percentage); + pkg_pb->set_distance(pkg_data.distance); + pkg_pb->set_info(pkg_data.info); + + for (auto& st_item : pkg_data.task_status_list) + { + auto* st_pb = pkg_pb->add_task_status_list(); + st_pb->set_task_id(st_item.task_id); + st_pb->set_status(st_item.status); + st_pb->set_type(st_item.type); + } + + auto* header = response->mutable_header(); + header->set_success(true); + header->set_error_message(""); + setCurrentTimestamp(header->mutable_timestamp()); + + LOG(INFO) << "RobotStatusTaskPackage dev_id:" << dev_id << " query task count:" << req.task_ids.size() << " ret_code:" << res.ret_code; + return grpc::Status::OK; + } + catch (std::exception &e) + { + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message(e.what()); + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } + } + + // ===================== RobotGoTarget ===================== + /** + * @brief 单点站点自动规划导航。 + * @param context 未使用。 + * @param request 包含设备 ID 和完整的导航参数(支持自由导航、动作、PGV、脚本等)。 + * @param response 返回下发结果。 + * @warning 严禁用于多车调度场景,仅限单车测试。 + */ + grpc::Status gRPCAGVServiceImpl::RobotGoTarget( + grpc::ServerContext* context, + const api::RobotGoTargetCommand_Request* request, + api::RobotGoTargetCommand_Feedback* response) + { + try + { + std::string dev_id = request->header().device_id(); + LOG(INFO) << "[RobotGoTarget] device_id:" << dev_id; + auto agv_dev = dmgr_.getDevice(dev_id); + if (!agv_dev) + { + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message("device not found"); + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } + + device::AbstractAgv::RobotGoTargetReq req; + auto& pb_data = request->data(); + + // -------- 必填字段 -------- + req.source_id = pb_data.source_id(); + req.id = pb_data.id(); + if (pb_data.has_task_id()) req.task_id = pb_data.task_id(); + + // -------- 运动参数 -------- + if (pb_data.has_angle()) req.angle = pb_data.angle(); + if (pb_data.has_method()) req.method = pb_data.method(); + if (pb_data.has_max_speed()) req.max_speed = pb_data.max_speed(); + if (pb_data.has_max_wspeed()) req.max_wspeed = pb_data.max_wspeed(); + if (pb_data.has_max_acc()) req.max_acc = pb_data.max_acc(); + if (pb_data.has_max_wacc()) req.max_wacc = pb_data.max_wacc(); + if (pb_data.has_duration()) req.duration = pb_data.duration(); + if (pb_data.has_orientation()) req.orientation = pb_data.orientation(); + if (pb_data.has_spin()) req.spin = pb_data.spin(); + if (pb_data.has_delay()) req.delay = pb_data.delay(); + if (pb_data.has_start_rot_dir()) req.start_rot_dir = pb_data.start_rot_dir(); + if (pb_data.has_end_rot_dir()) req.end_rot_dir = pb_data.end_rot_dir(); + if (pb_data.has_reach_dist()) req.reach_dist = pb_data.reach_dist(); + if (pb_data.has_reach_angle()) req.reach_angle = pb_data.reach_angle(); + if (pb_data.has_skill_name()) req.skill_name = pb_data.skill_name(); + + // -------- PGV 二次定位 -------- + if (pb_data.has_pgv()) + { + auto& pgv_pb = pb_data.pgv(); + req.pgv.use_pgv = pgv_pb.use_pgv(); + req.pgv.use_down_pgv = pgv_pb.use_down_pgv(); + if (pgv_pb.has_pgv_adjust_dist()) req.pgv.pgv_adjust_dist = pgv_pb.pgv_adjust_dist(); + if (pgv_pb.has_pgv_adjust_cx()) req.pgv.pgv_adjust_cx = pgv_pb.pgv_adjust_cx(); + if (pgv_pb.has_pgv_adjust_cy()) req.pgv.pgv_adjust_cy = pgv_pb.pgv_adjust_cy(); + if (pgv_pb.has_pgv_x_adjust()) req.pgv.pgv_x_adjust = pgv_pb.pgv_x_adjust(); + } + + // -------- 设备动作 -------- + if (pb_data.has_operation()) req.operation = pb_data.operation(); + if (pb_data.has_jack_height()) req.jack_height = pb_data.jack_height(); + if (pb_data.has_start_height()) req.start_height = pb_data.start_height(); + if (pb_data.has_end_height()) req.end_height = pb_data.end_height(); + if (pb_data.has_fork_mid_height()) req.fork_mid_height = pb_data.fork_mid_height(); + if (pb_data.has_fork_dist()) req.fork_dist = pb_data.fork_dist(); + if (pb_data.has_direction()) req.direction = pb_data.direction(); + if (pb_data.has_recognize()) req.recognize = pb_data.recognize(); + if (pb_data.has_recfile()) req.recfile = pb_data.recfile(); + + // -------- 音频 -------- + if (pb_data.has_sounds_args()) + { + auto& s_pb = pb_data.sounds_args(); + if (s_pb.has_name()) req.sounds_args.name = s_pb.name(); + if (s_pb.has_loop()) req.sounds_args.loop = s_pb.loop(); + if (s_pb.has_stop()) req.sounds_args.stop = s_pb.stop(); + } + + // -------- WaitDI -------- + if (pb_data.has_wait_di_args()) + { + auto& di_pb = pb_data.wait_di_args(); + req.wait_di_args.timeout = di_pb.timeout(); + for (auto& item : di_pb.di()) + { + device::AbstractAgv::DIItem di; + di.id = item.id(); + di.status = item.status(); + req.wait_di_args.DI.push_back(di); + } + } + + // -------- SetDO -------- + if (pb_data.has_set_do_args()) + { + auto& do_pb = pb_data.set_do_args(); + for (auto& item : do_pb.do_list()) + { + device::AbstractAgv::DOItem d; + d.id = item.id(); + d.status = item.status(); + req.set_do_args.DO.push_back(d); + } + } + + // -------- 脚本 -------- + if (pb_data.has_script_name()) req.script_name = pb_data.script_name(); + if (pb_data.has_script_stage()) req.script_stage = pb_data.script_stage(); + if (pb_data.has_script_args()) + { + auto& s_arg_pb = pb_data.script_args(); + for (auto& kv : s_arg_pb.str_kv()) req.script_args.str_kv[kv.first] = kv.second; + for (auto& kv : s_arg_pb.num_kv()) req.script_args.num_kv[kv.first] = kv.second; + for (auto& d : s_arg_pb.do_list()) + { + device::AbstractAgv::DOItem di; + di.id = d.id(); + di.status = d.status(); + req.script_args.do_list.push_back(di); + } + for (auto& di : s_arg_pb.di_list()) + { + device::AbstractAgv::DIItem d; + d.id = di.id(); + d.status = di.status(); + req.script_args.di_list.push_back(d); + } + } + + // -------- 原地旋转 -------- + if (pb_data.has_move_angle()) req.move_angle = pb_data.move_angle(); + if (pb_data.has_speed_w()) req.speed_w = pb_data.speed_w(); + if (pb_data.has_loc_mode()) req.loc_mode = pb_data.loc_mode(); + + // -------- 自由导航 -------- + if (pb_data.has_freego()) + { + auto& fg_pb = pb_data.freego(); + req.freeGo.x = fg_pb.x(); + req.freeGo.y = fg_pb.y(); + req.freeGo.theta = fg_pb.theta(); + } + + // 调用设备层 + device::AbstractAgv::RobotGoTargetRes res; + agv_dev->robotGoTarget(res, req); + + auto* out_data = response->mutable_data(); + out_data->set_ret_code(res.ret_code); + out_data->set_create_on(res.create_on); + out_data->set_err_msg(res.err_msg); + + auto* header = response->mutable_header(); + header->set_success(true); + header->set_error_message(""); + setCurrentTimestamp(header->mutable_timestamp()); + + LOG(INFO) << "[RobotGoTarget] finish ret_code=" << res.ret_code; + return grpc::Status::OK; + } + catch (std::exception& e) + { + auto* header = response->mutable_header(); + header->set_success(false); + header->set_error_message(std::string("exception:") + e.what()); + setCurrentTimestamp(header->mutable_timestamp()); + return grpc::Status::OK; + } + } + + // ===================== RobotControlStop ===================== + /** + * @brief 停止开环运动。 + * @param context 未使用。 + * @param request 包含设备 ID(无业务数据)。 + * @param response 返回停止结果。 + */ + grpc::Status gRPCAGVServiceImpl::RobotControlStop( + grpc::ServerContext* context, + const api::RobotControlStopCommand::Request* request, + api::RobotControlStopCommand::Feedback* response) { + + const auto& header = request->header(); + std::string device_id = header.device_id(); + auto* fb_header = response->mutable_header(); + + if (device_id.empty()) { + fb_header->set_error_message("Missing device_id"); + return grpc::Status::OK; + } + + auto agv = dmgr_.getDevice(device_id); + if (!agv) { + fb_header->set_error_message("Device not found or not an AGV"); + return grpc::Status::OK; + } + + cmvr::device::AbstractAgv::RobotControlStopRes res; + agv->robotControlStop(res); + + auto* status = response->mutable_status(); + status->set_ret_code(res.ret_code); + status->set_create_on(res.create_on); + status->set_err_msg(res.err_msg); + + if (res.ret_code == 0) { + fb_header->set_error_message(""); + } else { + fb_header->set_error_message(res.err_msg.empty() ? "Stop motion failed" : res.err_msg); + } + + return grpc::Status::OK; + } + + // ===================== RobotTaskPause ===================== + /** + * @brief 暂停当前导航任务。 + * @param context 未使用。 + * @param request 包含设备 ID。 + * @param response 返回暂停结果。 + */ + grpc::Status gRPCAGVServiceImpl::RobotTaskPause( + grpc::ServerContext* context, + const api::RobotTaskPauseCommand::Request* request, + api::RobotTaskPauseCommand::Feedback* response) { + + const auto& header = request->header(); + std::string device_id = header.device_id(); + auto* fb_header = response->mutable_header(); + + if (device_id.empty()) { + fb_header->set_error_message("Missing device_id"); + return grpc::Status::OK; + } + + auto agv = dmgr_.getDevice(device_id); + if (!agv) { + fb_header->set_error_message("Device not found or not an AGV"); + return grpc::Status::OK; + } + + cmvr::device::AbstractAgv::RobotTaskPauseRes res; + try { + agv->robotTaskPause(res); + } catch (const std::exception& e) { + fb_header->set_error_message(std::string("Exception: ") + e.what()); + return grpc::Status::OK; + } + + auto* status = response->mutable_status(); + status->set_ret_code(res.ret_code); + status->set_create_on(res.create_on); + status->set_err_msg(res.err_msg); + + if (res.ret_code != 0) { + fb_header->set_error_message(res.err_msg.empty() ? "Pause task failed" : res.err_msg); + } else { + fb_header->set_error_message(""); + } + + return grpc::Status::OK; + } + + // ===================== RobotTaskResume ===================== + /** + * @brief 继续当前导航任务。 + * @param context 未使用。 + * @param request 包含设备 ID。 + * @param response 返回继续结果。 + */ + grpc::Status gRPCAGVServiceImpl::RobotTaskResume( + grpc::ServerContext* context, + const api::RobotTaskResumeCommand::Request* request, + api::RobotTaskResumeCommand::Feedback* response) { + + const auto& header = request->header(); + std::string device_id = header.device_id(); + auto* fb_header = response->mutable_header(); + + if (device_id.empty()) { + fb_header->set_error_message("Missing device_id"); + return grpc::Status::OK; + } + + auto agv = dmgr_.getDevice(device_id); + if (!agv) { + fb_header->set_error_message("Device not found or not an AGV"); + return grpc::Status::OK; + } + + cmvr::device::AbstractAgv::RobotTaskResumeRes res; + try { + agv->robotTaskResume(res); + } catch (const std::exception& e) { + fb_header->set_error_message(std::string("Exception: ") + e.what()); + return grpc::Status::OK; + } + + auto* status = response->mutable_status(); + status->set_ret_code(res.ret_code); + status->set_create_on(res.create_on); + status->set_err_msg(res.err_msg); + + if (res.ret_code != 0) { + fb_header->set_error_message(res.err_msg.empty() ? "Resume task failed" : res.err_msg); + } else { + fb_header->set_error_message(""); + } + + return grpc::Status::OK; + } + + // ===================== RobotTaskCancel ===================== + /** + * @brief 取消当前导航任务。 + * @param context 未使用。 + * @param request 包含设备 ID。 + * @param response 返回取消结果。 + */ + grpc::Status gRPCAGVServiceImpl::RobotTaskCancel( + grpc::ServerContext* context, + const api::RobotTaskCancelCommand::Request* request, + api::RobotTaskCancelCommand::Feedback* response) { + + const auto& header = request->header(); + std::string device_id = header.device_id(); + auto* fb_header = response->mutable_header(); + + if (device_id.empty()) { + fb_header->set_error_message("Missing device_id"); + return grpc::Status::OK; + } + + auto agv = dmgr_.getDevice(device_id); + if (!agv) { + fb_header->set_error_message("Device not found or not an AGV"); + return grpc::Status::OK; + } + + cmvr::device::AbstractAgv::RobotTaskCancelRes res; + try { + agv->robotTaskCancel(res); + } catch (const std::exception& e) { + fb_header->set_error_message(std::string("Exception: ") + e.what()); + return grpc::Status::OK; + } + + auto* status = response->mutable_status(); + status->set_ret_code(res.ret_code); + status->set_create_on(res.create_on); + status->set_err_msg(res.err_msg); + + if (res.ret_code != 0) { + fb_header->set_error_message(res.err_msg.empty() ? "Cancel task failed" : res.err_msg); + } else { + fb_header->set_error_message(""); + } + + return grpc::Status::OK; + } + +} // namespace cmvr::service \ No newline at end of file diff --git a/protos/cmvr/api/agv_command.proto b/protos/cmvr/api/agv_command.proto index d2a4fad9..8cfe89a1 100644 --- a/protos/cmvr/api/agv_command.proto +++ b/protos/cmvr/api/agv_command.proto @@ -1,109 +1,161 @@ +/** + * @file agv_command.proto + * @brief AGV 控制相关的 Protobuf 消息定义。 + * 涵盖仙工智能(Seer)SRC 系列控制器的主要 API 命令, + * 包括状态查询、电池、位置、地图管理、控制权、运动控制、导航任务等。 + * @note 所有命令均遵循仙工协议帧格式,通过 gRPC 服务(AgvService)对外暴露。 + */ + syntax = "proto3"; import "cmvr/api/common.proto"; package cmvr.api; -// AGV状态信息 +// ============================================================================ +// 1. 基本信息查询(命令码 1000, 0x03E8) +// ============================================================================ + +/** + * @brief AGV 基本信息响应结构。 + * @note 对应仙工 API 1000(0x03E8)的响应体,包含设备标识、软硬件版本、网络状态等。 + */ message AgvStatusInfo { - optional string id = 1; // AGV ID - optional string vehicle_id = 2; // 车辆ID - optional string version = 3; // 软件版本 - optional string model = 4; // 型号 - optional string dsp_version = 5; // DSP版本 - optional string current_ip = 6; // 当前IP地址 - optional string mac = 7; // MAC地址 - optional int32 rssi = 8; // 信号强度 - optional int32 ret_code = 9; // 返回码 - optional string err_msg = 10; // 错误信息 + optional string id = 1; ///< AGV 设备唯一 ID + optional string vehicle_id = 2; ///< 车辆编号(如 "agv_001") + optional string version = 3; ///< 软件版本号 + optional string model = 4; ///< 车型型号(如 "SRC-1100") + optional string dsp_version = 5; ///< DSP 固件版本 + optional string current_ip = 6; ///< 当前 IP 地址 + optional string mac = 7; ///< MAC 地址 + optional int32 rssi = 8; ///< Wi-Fi 信号强度(0~100) + optional int32 ret_code = 9; ///< 返回码,0 表示成功 + optional string err_msg = 10; ///< 错误信息(失败时填充) } -// 获取AGV状态命令 +/** + * @brief 获取 AGV 状态命令(请求/反馈)。 + * @note 请求携带设备 ID(header.device_id),反馈返回 AgvStatusInfo。 + */ message GetAgvStatusInfoCommand { message Request { - CommandHeader.Request header = 1; + CommandHeader.Request header = 1; ///< 公共请求头,包含 device_id } message Feedback { - CommandHeader.Feedback header = 1; - AgvStatusInfo status = 2; + CommandHeader.Feedback header = 1; ///< 公共响应头(success/error_message/timestamp) + AgvStatusInfo status = 2; ///< AGV 状态信息 } } -// ===================== 电池状态查询 ===================== +// ============================================================================ +// 2. 电池状态查询(命令码 1007, 0x03EF) +// ============================================================================ + +/** + * @brief 电池状态响应结构。 + * @note 对应仙工 API 1007(0x03EF),支持简单/全量两种模式。 + */ message AgvBatteryStatus { - optional double battery_level = 1; // 电池电量 [0,1] - optional double battery_temp = 2; // 电池温度 ℃ - optional bool charging = 3; // 是否正在充电 - optional double voltage = 4; // 电压 V - optional double current = 5; // 电流 A - optional double max_charge_voltage = 6; // 最大充电电压,-1=不支持 - optional double max_charge_current = 7; // 最大充电电流,-1=不支持 - optional bool manual_charge = 8; // 手动充电状态 - optional bool auto_charge = 9; // 自动充电状态 - optional int32 battery_cycle = 10; // 电池循环次数 - optional string battery_user_data = 11; // 电池自定义数据 - optional string extra = 12; // 客户自定义数据 - optional int32 ret_code = 13; // 错误码 - optional string create_on = 14; // 时间戳 - optional string err_msg = 15; // 错误信息 + optional double battery_level = 1; ///< 电池电量(0~1,对应 0%~100%) + optional double battery_temp = 2; ///< 电池温度(摄氏度) + optional bool charging = 3; ///< 是否正在充电 + optional double voltage = 4; ///< 当前电压(V) + optional double current = 5; ///< 当前电流(A) + optional double max_charge_voltage = 6; ///< 最大充电电压(-1 表示不支持) + optional double max_charge_current = 7; ///< 最大充电电流(-1 表示不支持) + optional bool manual_charge = 8; ///< 是否连接手动充电器(仅 SRC-2000) + optional bool auto_charge = 9; ///< 是否连接自动充电桩(仅 SRC-2000) + optional int32 battery_cycle = 10; ///< 电池循环次数(从 BMS 读取,可能不准) + optional string battery_user_data = 11; ///< 电池用户自定义数据 + optional string extra = 12; ///< 扩展字段(预留) + optional int32 ret_code = 13; ///< 返回码,0 表示成功 + optional string create_on = 14; ///< 数据创建时间戳(ISO 8601) + optional string err_msg = 15; ///< 错误信息 } +/** + * @brief 电池状态请求数据。 + */ message RobotStatusBatteryRequestData { - optional bool simple = 1; // true:仅简单数据 false:全量数据,默认false + optional bool simple = 1; ///< true=仅关键数据,false=全量数据(默认 false) } +/** + * @brief 电池状态查询命令。 + */ message RobotStatusBatteryCommand { message Request { - CommandHeader.Request header = 1; - RobotStatusBatteryRequestData data = 2; + CommandHeader.Request header = 1; ///< 公共请求头 + RobotStatusBatteryRequestData data = 2; ///< 请求参数 } message Feedback { - CommandHeader.Feedback header = 1; - AgvBatteryStatus status = 2; + CommandHeader.Feedback header = 1; ///< 公共响应头 + AgvBatteryStatus status = 2; ///< 电池状态信息 } } +// ============================================================================ +// 3. 机器人位置查询(命令码 1004, 0x03EC) +// ============================================================================ -// ===================== 机器人位置查询 ===================== +/** + * @brief 机器人位置响应结构。 + * @note 对应仙工 API 1004(0x03EC),返回世界坐标系下的位姿。 + */ message AgvRobotLocation { - optional double x = 1; // X坐标(m) - optional double y = 2; // Y坐标(m) - optional double angle = 3; // 姿态角(rad) - optional double confidence = 4; // 定位置信度[0,1] - optional string current_station = 5; // 当前最近站点ID - optional string last_station = 6; // 上一站点ID - optional int32 loc_method = 7; // 定位方式 - optional int32 ret_code = 8; // 错误码 - optional string create_on = 9; // 时间戳 - optional string err_msg = 10; // 错误信息 + optional double x = 1; ///< X 坐标(米) + optional double y = 2; ///< Y 坐标(米) + optional double angle = 3; ///< 朝向角(弧度),逆时针为正 + optional double confidence = 4; ///< 定位置信度(0~1) + optional string current_station = 5; ///< 当前所在站点 ID(若在站点上) + optional string last_station = 6; ///< 上一个经过的站点 ID + optional int32 loc_method = 7; ///< 定位方式(0=自然轮廓, 1=反光柱, 2=二维码, 3=里程计...) + optional int32 ret_code = 8; ///< 返回码,0 表示成功 + optional string create_on = 9; ///< 时间戳 + optional string err_msg = 10; ///< 错误信息 } +/** + * @brief 位置查询命令(无请求数据)。 + */ message RobotStatusLocCommand { message Request { - CommandHeader.Request header = 1; + CommandHeader.Request header = 1; ///< 公共请求头 } message Feedback { - CommandHeader.Feedback header = 1; - AgvRobotLocation status = 2; + CommandHeader.Feedback header = 1; ///< 公共响应头 + AgvRobotLocation status = 2; ///< 位置信息 } } -// ===================== 地图下载 ===================== +// ============================================================================ +// 4. 地图下载(命令码 4011, 0x0FAB) +// ============================================================================ + +/** + * @brief 地图下载请求数据。 + */ message RobotConfigDownloadMapRequestData { - optional string map_name = 1; + optional string map_name = 1; ///< 要下载的地图名称(必填) } +/** + * @brief 地图下载响应结果。 + */ message AgvDownloadMapResult { - optional int32 ret_code = 1; - optional string create_on = 2; - optional string err_msg = 3; - optional string map_content = 4; + optional int32 ret_code = 1; ///< 返回码,0 表示成功 + optional string create_on = 2; ///< 时间戳 + optional string err_msg = 3; ///< 错误信息 + optional string map_content = 4; ///< 地图 JSON 文件内容(成功时填充) } +/** + * @brief 地图下载命令。 + */ message RobotConfigDownloadMapCommand { message Request { CommandHeader.Request header = 1; @@ -116,25 +168,36 @@ message RobotConfigDownloadMapCommand { } -// 单条地图文件信息 +// ============================================================================ +// 5. 地图状态查询(命令码 1300, 0x0514) +// ============================================================================ + +/** + * @brief 单个地图文件信息。 + */ message MapFileInfo { - optional string name = 1; - optional string modified = 2; - optional int64 size = 3; + optional string name = 1; ///< 文件名 + optional string modified = 2; ///< 最后修改时间 + optional int64 size = 3; ///< 文件大小(字节) } -// 地图查询响应 +/** + * @brief 地图状态响应结构。 + * @note 对应仙工 API 1300(0x0514),返回当前地图及所有地图列表。 + */ message AgvMapStatus { - optional string current_map = 1; - optional string current_map_md5 = 2; - repeated string maps = 3; - repeated MapFileInfo map_files_info = 4; - optional int32 ret_code = 5; - optional string create_on = 6; - optional string err_msg = 7; + optional string current_map = 1; ///< 当前加载的地图名称 + optional string current_map_md5 = 2; ///< 当前地图的 MD5 校验值 + repeated string maps = 3; ///< 所有地图名称列表 + repeated MapFileInfo map_files_info = 4; ///< 每个地图文件的详细信息 + optional int32 ret_code = 5; ///< 返回码,0 表示成功 + optional string create_on = 6; ///< 时间戳 + optional string err_msg = 7; ///< 错误信息 } -// 查询地图状态命令 +/** + * @brief 地图状态查询命令(无请求数据)。 + */ message RobotStatusMapCommand { message Request { CommandHeader.Request header = 1; @@ -146,17 +209,29 @@ message RobotStatusMapCommand { } -// ===================== 上传地图 ===================== +// ============================================================================ +// 6. 上传地图(命令码 4010, 0x0FAA) +// ============================================================================ + +/** + * @brief 上传地图请求数据。 + */ message RobotConfigUploadMapRequestData { - optional string map_content = 1; + optional string map_content = 1; ///< 完整的地图 JSON 字符串(必填) } +/** + * @brief 上传地图响应结果。 + */ message AgvUploadMapResult { - optional int32 ret_code = 1; - optional string create_on = 2; - optional string err_msg = 3; + optional int32 ret_code = 1; ///< 返回码,0 表示成功 + optional string create_on = 2; ///< 时间戳 + optional string err_msg = 3; ///< 错误信息 } +/** + * @brief 上传地图命令。 + */ message RobotConfigUploadMapCommand { message Request { CommandHeader.Request header = 1; @@ -169,67 +244,65 @@ message RobotConfigUploadMapCommand { } -// ===================== 抢占控制权 ====================== -// 抢占控制权 请求参数 +// ============================================================================ +// 7. 抢占控制权(命令码 4005, 0x0FA5) +// ============================================================================ + +/** + * @brief 抢占控制权请求数据。 + */ message RobotConfigLockRequestData { - // 控制权抢占者名称,必填 - optional string nick_name = 1; + optional string nick_name = 1; ///< 抢占者昵称/标识(必填) } -// 抢占控制权 返回结果 +/** + * @brief 抢占控制权响应结果。 + */ message AgvLockResult { - // API错误码 - optional int32 ret_code = 1; - // API上传时间戳 - optional string create_on = 2; - // 错误描述信息 - optional string err_msg = 3; + optional int32 ret_code = 1; ///< 返回码,0 表示成功 + optional string create_on = 2; ///< 时间戳 + optional string err_msg = 3; ///< 错误信息 } -// 抢占控制权 命令封装 +/** + * @brief 抢占控制权命令。 + */ message RobotConfigLockCommand { message Request { CommandHeader.Request header = 1; - // 请求业务参数 RobotConfigLockRequestData data = 2; } message Feedback { CommandHeader.Feedback header = 1; - // 抢占控制权响应数据 AgvLockResult status = 2; } } +// ============================================================================ +// 8. 查询当前控制权所有者(命令码 1060, 0x0424) +// ============================================================================ -// ===================== 查询当前控制权所有者 ====================== -// 查询当前控制权所有者响应结构体 +/** + * @brief 当前控制权所有者响应结构。 + * @note 对应仙工 API 1060(0x0424)。 + */ message AgvCurrentLockStatus { - // 当前控制权是否被抢占 - optional bool locked = 1; - // 抢占方IP - optional string ip = 2; - // 抢占方端口 - optional int32 port = 3; - // 抢占者类型 0=default 2=roboshop 0xDD=srd - optional uint32 type = 4; - // 抢占者昵称 - optional string nick_name = 5; - // 抢占时间戳 单位秒 - optional int64 time_t = 6; - // 抢占方描述信息 - optional string desc = 7; - // 接口错误码 - optional int32 ret_code = 8; - // 接口时间戳 - optional string create_on = 9; - // 错误信息 - optional string err_msg = 10; + optional bool locked = 1; ///< 是否被锁定(有持有者) + optional string ip = 2; ///< 持有者 IP 地址 + optional int32 port = 3; ///< 持有者端口 + optional uint32 type = 4; ///< 持有者类型(0=default, 2=roboshop, 0xDD=srd) + optional string nick_name = 5; ///< 持有者昵称 + optional int64 time_t = 6; ///< 锁定时间戳(Unix 秒) + optional string desc = 7; ///< 描述信息 + optional int32 ret_code = 8; ///< 返回码,0 表示成功 + optional string create_on = 9; ///< 时间戳 + optional string err_msg = 10; ///< 错误信息 } - - -// 查询当前控制权指令 +/** + * @brief 查询控制权命令(无请求数据)。 + */ message RobotStatusCurrentLockCommand { message Request { CommandHeader.Request header = 1; @@ -241,27 +314,37 @@ message RobotStatusCurrentLockCommand { } -//==================== 开环速度运动控制 命令码:2010(0x07DA) ==================== -// 下发指令会强制取消当前正在执行的自动导航任务 -// 多舵轮设备仅解析vx/vy/w;steer、real_steer仅单舵轮设备生效 -// vx:X轴速度 m/s;vy:Y轴速度 m/s;w:旋转角速度 rad/s,逆时针为正、顺时针为负 -// steer:单舵轮档位控制 -2回零 / ±15°增减;real_steer:目标舵角rad,优先级高于steer -// duration:持续时间ms,0=永久运行;缺省读取全局参数ControlMotionDuration(默认500ms) +// ============================================================================ +// 9. 开环速度运动控制(命令码 2010, 0x07DA) +// ============================================================================ + +/** + * @brief 开环速度运动控制请求数据。 + * @note 命令码 2010(0x07DA)。下发后会强制取消当前自动导航任务。 + * 多舵轮设备仅 vx/vy/w 生效;steer/real_steer 仅单舵轮设备有效。 + * duration = -1 表示持续运行直到下一次指令。 + */ message RobotMotionControlRequestData { - optional double vx = 1; - optional double vy = 2; - optional double w = 3; - optional double steer = 4; - optional double real_steer = 5; - optional int64 duration = 6; + optional double vx = 1; ///< X 方向线速度(m/s) + optional double vy = 2; ///< Y 方向线速度(m/s) + optional double w = 3; ///< 角速度(rad/s),逆时针为正 + optional double steer = 4; ///< 舵轮转向角(单舵轮,单位 rad) + optional double real_steer = 5; ///< 实际舵轮角度(单舵轮,优先级高于 steer) + optional int64 duration = 6; ///< 持续时间(ms),-1 表示一直运行 } +/** + * @brief 开环运动控制响应结果。 + */ message RobotMotionControlResult { - optional int32 ret_code = 1; - optional string create_on = 2; - optional string err_msg = 3; + optional int32 ret_code = 1; ///< 返回码,0 表示成功 + optional string create_on = 2; ///< 时间戳 + optional string err_msg = 3; ///< 错误信息 } +/** + * @brief 开环运动控制命令。 + */ message RobotMotionControlCommand { message Request { CommandHeader.Request header = 1; @@ -274,18 +357,30 @@ message RobotMotionControlCommand { } -//==================== 切换载入地图 命令码:2022(0x07E6) ==================== -// 切换指定地图,合法地图会更新设备current_map字段 +// ============================================================================ +// 10. 切换载入地图(命令码 2022, 0x07E6) +// ============================================================================ + +/** + * @brief 切换地图请求数据。 + * @note 命令码 2022(0x07E6)。目标地图必须已存在于机器人中。 + */ message RobotLoadMapRequestData { - optional string map_name = 1; // 地图名,仅字母数字-_,不可缺省 + optional string map_name = 1; ///< 目标地图名称(必填,仅字母数字-_) } +/** + * @brief 切换地图响应结果。 + */ message RobotLoadMapResult { - optional int32 ret_code = 1; - optional string create_on = 2; - optional string err_msg = 3; + optional int32 ret_code = 1; ///< 返回码,0 表示成功 + optional string create_on = 2; ///< 时间戳 + optional string err_msg = 3; ///< 错误信息 } +/** + * @brief 切换地图命令。 + */ message RobotLoadMapCommand { message Request { CommandHeader.Request header = 1; @@ -298,15 +393,25 @@ message RobotLoadMapCommand { } -//==================== 查询地图载入状态 命令码:1022(0x03FE) ==================== -// loadmap_status:0失败 1成功 2载入中;LOADING状态不可重定位 +// ============================================================================ +// 11. 查询地图载入状态(命令码 1022, 0x03FE) +// ============================================================================ + +/** + * @brief 地图载入状态响应。 + * @note 命令码 1022(0x03FE)。loadmap_status: 0=失败, 1=成功, 2=载入中。 + * 载入中(2)时禁止执行重定位操作。 + */ message RobotQueryLoadMapStatusResult { - optional int32 loadmap_status = 1; - optional int32 ret_code = 2; - optional string create_on = 3; - optional string err_msg = 4; + optional int32 loadmap_status = 1; ///< 0=失败, 1=成功, 2=载入中 + optional int32 ret_code = 2; ///< 返回码,0 表示成功 + optional string create_on = 3; ///< 时间戳 + optional string err_msg = 4; ///< 错误信息 } +/** + * @brief 查询地图载入状态命令(无请求数据)。 + */ message RobotQueryLoadMapStatusCommand { message Request { CommandHeader.Request header = 1; @@ -318,29 +423,40 @@ message RobotQueryLoadMapStatusCommand { } +// ============================================================================ +// 12. 查询站点列表(命令码 1301, 0x0515) +// ============================================================================ -//==================== 查询当前地图所有站点信息 命令码:1301(0x0515) ==================== +/** + * @brief 单个站点信息。 + */ message StationItem { - optional string id = 1; - optional string type = 2; - optional double x = 3; - optional double y = 4; - optional double r = 5; - optional string desc = 6; - optional string executor = 7; - optional string prepoint = 8; - optional string recfile = 9; - optional bool spin = 10; - optional bool use_down_pgv = 11; + optional string id = 1; ///< 站点唯一 ID + optional string type = 2; ///< 站点类型(如 "LocationMark", "ChargePoint") + optional double x = 3; ///< X 坐标(米) + optional double y = 4; ///< Y 坐标(米) + optional double r = 5; ///< 朝向角(弧度) + optional string desc = 6; ///< 描述信息 + optional string executor = 7; ///< 执行器信息 + optional string prepoint = 8; ///< 前置点 ID + optional string recfile = 9; ///< 识别文件路径 + optional bool spin = 10; ///< 是否允许自旋 + optional bool use_down_pgv = 11; ///< 是否使用下视 PGV } +/** + * @brief 站点列表响应结果。 + */ message QueryStationListResult { - repeated StationItem stations = 1; - optional int32 ret_code = 2; - optional string create_on = 3; - optional string err_msg = 4; + repeated StationItem stations = 1; ///< 站点列表 + optional int32 ret_code = 2; ///< 返回码,0 表示成功 + optional string create_on = 3; ///< 时间戳 + optional string err_msg = 4; ///< 错误信息 } +/** + * @brief 查询站点列表命令(无请求数据)。 + */ message QueryStationListCommand { message Request { CommandHeader.Request header = 1; @@ -351,28 +467,44 @@ message QueryStationListCommand { } } -//==================== 指定路径导航 命令码:3066(0x0BFA) ==================== -// 单段导航任务单元 + +// ============================================================================ +// 13. 指定路径导航(命令码 3066, 0x0BFA) +// ============================================================================ + +/** + * @brief 单段导航任务单元。 + * @note 用于 3066 命令的 move_task_list 数组。 + * source_id 和 id 之间必须有直接路径,不可跳点。 + */ message MoveTaskItem { - optional string task_id = 1; - optional string source_id = 2; - optional string id = 3; - optional string operation = 4; - optional double jack_height = 5; + optional string task_id = 1; ///< 任务唯一 ID(必填) + optional string source_id = 2; ///< 起始站点 ID(必填) + optional string id = 3; ///< 目标站点 ID(必填) + optional string operation = 4; ///< 到达后执行的动作(如 "JackLoad") + optional double jack_height = 5; ///< 顶升高度(米) } -// 指定路径导航请求体 +/** + * @brief 指定路径导航请求数据。 + */ message RobotGoTargetListRequestData { - repeated MoveTaskItem move_task_list = 1; + repeated MoveTaskItem move_task_list = 1; ///< 导航任务序列(顺序执行) } -// 指定路径导航返回结果 +/** + * @brief 指定路径导航响应结果。 + * @note ret_code=0 仅表示指令被接收,不表示执行完成。 + */ message RobotGoTargetListResult { - optional int32 ret_code = 1; - optional string create_on = 2; - optional string err_msg = 3; + optional int32 ret_code = 1; ///< 返回码,0 表示成功接收 + optional string create_on = 2; ///< 时间戳 + optional string err_msg = 3; ///< 错误信息 } +/** + * @brief 指定路径导航命令。 + */ message RobotGoTargetListCommand { message Request { CommandHeader.Request header = 1; @@ -385,40 +517,390 @@ message RobotGoTargetListCommand { } -//==================== 查询当前导航状态 robot_status_task_req 1020(0x03FC) 应答0x2B0C ==================== -message QueryCurrentNavReqData { - optional bool simple = 1; +// ============================================================================ +// 14. 查询当前实时导航状态(命令码 1020, 0x03FC) +// ============================================================================ + +/** + * @brief 1020 命令请求数据。 + */ +message RobotStatusTaskReqData { + optional bool simple = 1; ///< true=仅返回 task_status,false=全量 } +/** + * @brief 导航任务中的料箱信息。 + */ message NavContainerItem { - optional string container_name = 1; - optional string desc = 2; - optional string goods_id = 3; - optional bool has_goods = 4; + optional string container_name = 1; ///< 料箱名称 + optional string desc = 2; ///< 描述 + optional string goods_id = 3; ///< 绑定的货物 ID + optional bool has_goods = 4; ///< 是否已绑定货物 } -message QueryCurrentNavResData { - optional int32 task_status = 1; - optional int32 task_type = 2; - optional string target_id = 3; - repeated double target_point = 4; - repeated string finished_path = 5; - repeated string unfinished_path = 6; - optional string move_status_info = 7; - repeated NavContainerItem containers = 8; - - optional int32 ret_code = 9; - optional string create_on = 10; - optional string err_msg = 11; +/** + * @brief 1020 命令响应数据。 + */ +message RobotStatusTaskResData { + optional int32 task_status = 1; ///< 0=NONE, 1=WAITING, 2=RUNNING, 3=SUSPENDED, 4=COMPLETED, 5=FAILED, 6=CANCELED + optional int32 task_type = 2; ///< 0=无, 1=自由导航到坐标, 2=自由导航到站点, 3=路径导航到站点, 7=平动转动 + optional string target_id = 3; ///< 目标站点 ID(task_type 为 2/3 时) + repeated double target_point = 4; ///< 目标坐标 [x, y, r](task_type 为 1 时) + repeated string finished_path = 5; ///< 已经过的站点列表 + repeated string unfinished_path = 6; ///< 尚未经过的站点列表 + optional string move_status_info = 7; ///< 附加信息(如错误描述) + repeated NavContainerItem containers = 8; ///< 料箱信息 + optional int32 ret_code = 9; ///< 返回码,0 表示成功 + optional string create_on = 10; ///< 时间戳 + optional string err_msg = 11; ///< 错误信息 } -message RobotStatusTaskCommand { +/** + * @brief 查询当前导航状态命令。 + */ +message RobotStatusTaskCurrentCommand { message Request { CommandHeader.Request header = 1; - QueryCurrentNavReqData data = 2; + RobotStatusTaskReqData data = 2; } message Feedback { CommandHeader.Feedback header = 1; - QueryCurrentNavResData data = 2; + RobotStatusTaskResData data = 2; } } + + +// ============================================================================ +// 15. 批量查询任务状态(命令码 1110, 0x0456) +// ============================================================================ + +/** + * @brief 1110 命令请求数据。 + */ +message QueryTaskStatusPackageReqData { + repeated string task_ids = 1; ///< 要查询的任务 ID 列表;为空则查所有未完成 + 最近一条完成 +} + +/** + * @brief 单条任务状态项。 + */ +message SingleTaskStatusItem { + optional string task_id = 1; ///< 任务 ID + optional int32 status = 2; ///< 状态码(同 task_status) + optional int32 type = 3; ///< 任务类型(同 task_type) +} + +/** + * @brief 任务状态包(包含最近完成和所有未完成的任务)。 + */ +message TaskStatusPackage { + optional string closest_target = 1; ///< 最近站点 ID + optional string source_name = 2; ///< 起点站点名称 + optional string target_name = 3; ///< 终点站点名称 + optional double percentage = 4; ///< 任务进度百分比(0~100) + optional double distance = 5; ///< 剩余距离(米) + optional string info = 6; ///< 附加信息 + repeated SingleTaskStatusItem task_status_list = 7; ///< 所有任务状态列表 +} + +/** + * @brief 1110 命令响应数据。 + */ +message QueryTaskStatusPackageResData { + optional TaskStatusPackage task_status_package = 1; ///< 任务状态包 + optional int32 ret_code = 2; ///< 返回码,0 表示成功 + optional string create_on = 3; ///< 时间戳 + optional string err_msg = 4; ///< 错误信息 +} + +/** + * @brief 批量查询任务状态命令。 + */ +message RobotStatusTaskPackageCommand { + message Request { + CommandHeader.Request header = 1; + QueryTaskStatusPackageReqData data = 2; + } + message Feedback { + CommandHeader.Feedback header = 1; + QueryTaskStatusPackageResData data = 2; + } +} + + +// ============================================================================ +// 16. 单点站点自动规划导航(命令码 3051, 0x0BEB) +// ============================================================================ + +/** + * @brief DI(数字输入)点位。 + */ +message DIItem { + int32 id = 1; ///< DI 编号 + bool status = 2; ///< 状态(true=高电平, false=低电平) +} + +/** + * @brief DO(数字输出)点位。 + */ +message DOItem { + int32 id = 1; ///< DO 编号 + bool status = 2; ///< 状态(true=高电平, false=低电平) +} + +/** + * @brief 音频播放参数。 + */ +message SoundArgs { + optional string name = 1; ///< 音频文件名 + optional int32 loop = 2; ///< 循环次数(0=播放一次, 1=循环) + optional int32 stop = 3; ///< 1=停止播放 +} + +/** + * @brief WaitDI 动作参数。 + */ +message WaitDIArgs { + repeated DIItem DI = 1; ///< 需要等待的 DI 条件列表 + optional double timeout = 2; ///< 超时时间(秒),0 表示无限等待 +} + +/** + * @brief SetDO 动作参数。 + */ +message SetDOArgs { + repeated DOItem do_list = 1; ///< 要设置的 DO 列表 +} + +/** + * @brief PGV(二维码)二次定位参数。 + */ +message PgvParam { + optional bool use_pgv = 1; ///< 是否使用上视 PGV + optional bool use_down_pgv = 2; ///< 是否使用下视 PGV + optional double pgv_adjust_dist = 3; ///< 最大调整半径(米) + optional double pgv_adjust_cx = 4; ///< 二维码坐标系 X 偏移(米) + optional double pgv_adjust_cy = 5; ///< 二维码坐标系 Y 偏移(米) + optional double pgv_x_adjust = 6; ///< 仅调整 X 方向误差(米) +} + +/** + * @brief 自由导航目标点(坐标 + 朝向)。 + */ +message FreeGoPoint { + double x = 1; ///< X 坐标(米) + double y = 2; ///< Y 坐标(米) + double theta = 3; ///< 朝向角(弧度) +} + +/** + * @brief 脚本自定义参数。 + */ +message ScriptArgs { + map str_kv = 1; ///< 字符串键值对 + map num_kv = 2; ///< 数字键值对 + repeated DOItem do_list = 3; ///< DO 列表 + repeated DIItem di_list = 4; ///< DI 列表 +} + +/** + * @brief 3051 命令请求数据(单点站点自动规划导航)。 + * @warning 严禁用于多车调度场景,仅限单车测试/验证。 + * 下发此任务会取消当前正在执行的任务(不排队)。 + * 成功下发后会自动清除指定的 warning/error 报错码。 + * 自由导航(freego)仅支持双轮差速底盘。 + */ +message RobotGoTargetReqData { + // -------- 必填字段 -------- + string source_id = 1; ///< 起始站点 ID("SELF_POSITION" 表示当前位置) + string id = 2; ///< 目标站点 ID("SELF_POSITION" 表示原地执行 operation) + optional string task_id = 3; ///< 任务 ID(建议提供) + + // -------- 运动通用参数 -------- + optional double angle = 4; ///< 目标点朝向角(弧度) + optional string method = 5; ///< "forward"(正走)或 "backward"(倒走) + optional double max_speed = 6; ///< 最大线速度(m/s),0 使用默认 + optional double max_wspeed = 7; ///< 最大角速度(rad/s) + optional double max_acc = 8; ///< 最大加速度(m/s²) + optional double max_wacc = 9; ///< 最大角加速度(rad/s²) + optional int64 duration = 10; ///< 到达后等待时间(毫秒) + optional int32 orientation = 11; ///< 全向车保持的角度(暂不使用) + optional bool spin = 12; ///< 是否随动(托盘旋转) + optional int64 delay = 13; ///< 延迟结束导航状态(毫秒),缺省 0 + optional int32 start_rot_dir = 14; ///< 起步旋转方向:-1=顺时针, 0=就近, 1=逆时针 + optional int32 end_rot_dir = 15; ///< 到点旋转方向:-1=顺时针, 0=就近, 1=逆时针 + optional double reach_dist = 16; ///< 到点位置精度(米) + optional double reach_angle = 17; ///< 到点角度精度(弧度) + optional string skill_name = 18; ///< 技能名称:"Action" 或 "GotoSpecifiedPose" + + // -------- PGV -------- + optional PgvParam pgv = 19; ///< 二次定位参数 + + // -------- 设备动作 -------- + optional string operation = 20; ///< 操作类型(JackLoad/ForkUnload/RollerLoad/HookLoad/WaitDI/SetDO/sound/Script 等) + optional double jack_height = 21; ///< 顶升高度(米) + optional double start_height = 22; ///< 货叉起步前举升高度(米) + optional double end_height = 23; ///< 货叉到点后举升高度(米) + optional double fork_mid_height = 24; ///< 货叉行走过程中举升高度(米) + optional double fork_dist = 25; ///< 货叉前移距离(米) + optional string direction = 26; ///< 方向:"left"/"right"/"front"/"back" + optional bool recognize = 27; ///< 是否启用视觉识别 + optional string recfile = 28; ///< 识别文件路径(如 "shelf/s0002.shelf") + + // -------- 音频 -------- + optional SoundArgs sounds_args = 29; ///< 音频参数 + + // -------- WaitDI / SetDO -------- + optional WaitDIArgs wait_di_args = 30; ///< WaitDI 参数 + optional SetDOArgs set_do_args = 31; ///< SetDO 参数 + + // -------- 脚本 -------- + optional string script_name = 32; ///< 脚本文件名 + optional ScriptArgs script_args = 33; ///< 脚本参数 + optional int32 script_stage = 34; ///< 执行阶段:0=导航前, 1=导航中, 2=导航后, 3=脚本控制导航 + + // -------- 原地旋转(GoByOdometer) -------- + optional double move_angle = 35; ///< 旋转角度(弧度) + optional double speed_w = 36; ///< 旋转角速度(rad/s) + optional int32 loc_mode = 37; ///< 定位模式:1=激光定位, 0=里程定位 + + // -------- 自由导航(仅双轮差速) -------- + optional FreeGoPoint freego = 38; ///< 自由导航目标(若提供则忽略 id 字段) +} + +/** + * @brief 3051 命令响应数据。 + */ +message RobotGoTargetResData { + optional int32 ret_code = 1; ///< 返回码,0 表示成功接收 + optional string create_on = 2; ///< 时间戳 + optional string err_msg = 3; ///< 错误信息 +} + +/** + * @brief 单点导航命令。 + */ +message RobotGoTargetCommand { + message Request { + CommandHeader.Request header = 1; + RobotGoTargetReqData data = 2; + } + message Feedback { + CommandHeader.Feedback header = 1; + RobotGoTargetResData data = 2; + } +} + + +// ============================================================================ +// 17. 停止开环运动(命令码 2000, 0x07D0) +// ============================================================================ + +/** + * @brief 停止开环运动请求数据(无业务字段)。 + * @note 命令码 2000(0x07D0)。下发零速度指令,终止当前持续运动。 + */ +message RobotControlStopRequestData +{ +} + +/** + * @brief 停止开环运动响应结果。 + */ +message RobotControlStopResult +{ + optional int32 ret_code = 1; ///< 返回码,0 表示成功 + optional string create_on = 2; ///< 时间戳 + optional string err_msg = 3; ///< 错误信息 +} + +/** + * @brief 停止开环运动命令。 + */ +message RobotControlStopCommand +{ + message Request + { + CommandHeader.Request header = 1; + RobotControlStopRequestData data = 2; + } + message Feedback + { + CommandHeader.Feedback header = 1; + RobotControlStopResult status = 2; + } +} + + +// ============================================================================ +// 18. 暂停/继续/取消导航任务(命令码 3001/3002/3003) +// ============================================================================ + +/** + * @brief 暂停当前导航任务(命令码 3001, 0x0BB9)。 + * @note 请求无业务数据。 + */ +message RobotTaskPauseCommand { + message Request { + CommandHeader.Request header = 1; + // 无 data 字段 + } + message Feedback { + CommandHeader.Feedback header = 1; + /** + * @brief 暂停结果。 + */ + message Result { + optional int32 ret_code = 1; ///< 返回码,0 表示成功 + optional string create_on = 2; ///< 时间戳 + optional string err_msg = 3; ///< 错误信息 + } + Result status = 2; + } +} + +/** + * @brief 继续当前导航任务(命令码 3002, 0x0BBA)。 + * @note 请求无业务数据。 + */ +message RobotTaskResumeCommand { + message Request { + CommandHeader.Request header = 1; + // 无 data 字段 + } + message Feedback { + CommandHeader.Feedback header = 1; + /** + * @brief 继续结果。 + */ + message Result { + optional int32 ret_code = 1; ///< 返回码,0 表示成功 + optional string create_on = 2; ///< 时间戳 + optional string err_msg = 3; ///< 错误信息 + } + Result status = 2; + } +} + +/** + * @brief 取消当前导航任务(命令码 3003, 0x0BBB)。 + * @note 请求无业务数据。 + */ +message RobotTaskCancelCommand { + message Request { + CommandHeader.Request header = 1; + // 无 data 字段 + } + message Feedback { + CommandHeader.Feedback header = 1; + /** + * @brief 取消结果。 + */ + message Result { + optional int32 ret_code = 1; ///< 返回码,0 表示成功 + optional string create_on = 2; ///< 时间戳 + optional string err_msg = 3; ///< 错误信息 + } + Result status = 2; + } +} \ No newline at end of file diff --git a/protos/cmvr/api/agv_service.proto b/protos/cmvr/api/agv_service.proto index 6434904d..521ac025 100644 --- a/protos/cmvr/api/agv_service.proto +++ b/protos/cmvr/api/agv_service.proto @@ -1,3 +1,8 @@ +/** + * @file agv_service.proto + * @brief 定义AGV服务的gRPC接口,聚合所有AGV相关命令。 + * 服务端实现类为 gRPCAGVServiceImpl。 + */ syntax = "proto3"; import "cmvr/api/agv_command.proto"; @@ -45,16 +50,25 @@ service AgvService { rpc RobotGoTargetList(RobotGoTargetListCommand.Request) returns (RobotGoTargetListCommand.Feedback); // 查询当前导航状态 1020 robot_status_task_req - rpc RobotStatusTask(RobotStatusTaskCommand.Request) returns (RobotStatusTaskCommand.Feedback); + // 1020 查询当前实时导航 robot_status_task_req + rpc RobotStatusTaskCurrent(RobotStatusTaskCurrentCommand.Request) returns (RobotStatusTaskCurrentCommand.Feedback); + + // 批量查询任务状态 1110 robot_status_task_status_package_req + rpc RobotStatusTaskPackage(RobotStatusTaskPackageCommand.Request) returns (RobotStatusTaskPackageCommand.Feedback); + + + // 3051 单点站点自动规划导航 robot_task_gotarget_req 0x0BEB + rpc RobotGoTarget(RobotGoTargetCommand.Request) returns (RobotGoTargetCommand.Feedback); + // 0x07D0 停止开环运动 + rpc RobotControlStop(RobotControlStopCommand.Request) returns (RobotControlStopCommand.Feedback); - - - - - - -} - + // 暂停当前导航任务 命令码 3001 (0x0BB9) + rpc RobotTaskPause(RobotTaskPauseCommand.Request) returns (RobotTaskPauseCommand.Feedback); + // 继续当前导航任务 命令码 3002 (0x0BBA) + rpc RobotTaskResume(RobotTaskResumeCommand.Request) returns (RobotTaskResumeCommand.Feedback); + // 取消当前导航任务 命令码 3003 (0x0BBB) + rpc RobotTaskCancel(RobotTaskCancelCommand.Request) returns (RobotTaskCancelCommand.Feedback); +} \ No newline at end of file From a6d749f67933e7732fbf52b226a78b5c85f91a3b Mon Sep 17 00:00:00 2001 From: lgv Date: Thu, 25 Jun 2026 09:26:23 +0800 Subject: [PATCH 8/9] revert: rollback dev to fb304df94c --- CMakeLists.txt | 2 - .../config/agv_config/agv_config.pb.txt | 30 - .../biohead_config/biohead_config.pb.txt | 27 - .../config/camera_config/camera_config.pb.txt | 2 +- .../dexhand_config/dexhand_config.pb.txt | 6 +- .../monitor_config/monitor_config.pb.txt | 25 - .../config/robot_config/robot_config.pb.txt | 36 - .../config/server_config/server_config.pb.txt | 3 - .../config_helper/include/config_helper.h | 24 - .../config_helper/include/config_setting.h | 5 - .../config_helper/src/config_setting.cpp | 16 - .../common/utils/ffmpeg/include/FrameData.h | 53 - cmvr-es/data_center/CMakeLists.txt | 1 - cmvr-es/data_center/include/motors_info.h | 3 +- cmvr-es/data_center/src/motors_info.cpp | 156 +- cmvr-es/device_manager/CMakeLists.txt | 1 - .../device_manager/include/device_factory.h | 2 +- .../device_manager/include/device_manager.h | 2 +- cmvr-es/device_manager/src/device_factory.cpp | 56 +- cmvr-es/device_manager/src/device_manager.cpp | 66 +- cmvr-es/devices/CMakeLists.txt | 1 - cmvr-es/devices/agv/CMakeLists.txt | 2 - cmvr-es/devices/agv/abstract_agv.h | 881 +----- .../devices/agv/agv_src1100/CMakeLists.txt | 20 - .../agv/agv_src1100/include/agv_src1100.h | 307 -- .../agv/agv_src1100/src/agv_src1100.cpp | 2771 ----------------- cmvr-es/devices/biohead/abstract_biohead.h | 1 - .../biohead_esp32/include/biohead_esp32.h | 5 +- .../biohead_esp32/src/biohead_esp32.cpp | 213 -- cmvr-es/devices/canbus/abstract_canbus.h | 1 - .../socket/socket_can_client_raw.cc | 11 - .../can_client/socket/socket_can_client_raw.h | 4 - cmvr-es/devices/motor/CMakeLists.txt | 2 +- cmvr-es/devices/motor/abstract_motor.h | 1 - .../devices/motor/eyou_motor/CMakeLists.txt | 21 - .../ethercat/eyou_motor_ethercat_protocol.cpp | 747 ----- .../ethercat/eyou_motor_ethercat_protocol.h | 125 - .../ethercat/protocol/ec_master.cpp | 1143 ------- .../eyou_motor/ethercat/protocol/ec_master.h | 328 -- .../ethercat/protocol/ec_pdo_config.h | 75 - .../eyou_motor/ethercat/protocol/ec_types.h | 111 - .../ethercat/protocol/ec_xml_parser.cpp | 317 -- .../ethercat/protocol/ec_xml_parser.h | 41 - .../devices/motor/eyou_motor/eyou_motor.cpp | 5 - cmvr-es/devices/motor/eyou_motor/eyou_motor.h | 46 - .../devices/motor/motor_protocol_interface.h | 3 +- cmvr-es/devices/motor/ti5_motor/ti5_motor.h | 12 +- cmvr-es/devices/robot/abstract_robot.h | 1 - .../humanoid_robot/include/humanoid_robot.h | 9 +- .../humanoid_robot/src/humanoid_robot.cpp | 83 +- .../src/humanoid_robot_test.cpp | 18 - cmvr-es/main.cpp | 1 - cmvr-es/monitor/abstract_monitor.h | 1 - .../diskmonitor/include/disk_monitor.h | 6 +- .../monitor/diskmonitor/src/disk_monitor.cpp | 64 +- .../monitor_manager/include/monitor_factory.h | 15 +- .../monitor_manager/include/monitor_manager.h | 25 +- .../monitor_manager/src/monitor_factory.cpp | 28 +- .../monitor_manager/src/monitor_manager.cpp | 56 +- cmvr-es/service/CMakeLists.txt | 1 - .../service/grpc/include/grpc_agv_service.h | 278 -- cmvr-es/service/grpc/include/server_runner.h | 2 - cmvr-es/service/grpc/src/grpc_agv_service.cpp | 1208 ------- .../grpc/src/grpc_humanoid_robot_service.cpp | 2 +- cmvr-es/service/grpc/src/server_runner.cpp | 17 +- .../third_party/soem/include/soem/ec_base.h | 42 - .../third_party/soem/include/soem/ec_coe.h | 73 - .../third_party/soem/include/soem/ec_config.h | 31 - .../x86/third_party/soem/include/soem/ec_dc.h | 27 - .../third_party/soem/include/soem/ec_eoe.h | 213 -- .../third_party/soem/include/soem/ec_foe.h | 27 - .../third_party/soem/include/soem/ec_main.h | 624 ---- .../soem/include/soem/ec_options.h | 128 - .../third_party/soem/include/soem/ec_print.h | 30 - .../third_party/soem/include/soem/ec_soe.h | 121 - .../third_party/soem/include/soem/ec_type.h | 551 ---- .../third_party/soem/include/soem/nicdrv.h | 108 - .../x86/third_party/soem/include/soem/osal.h | 222 -- .../third_party/soem/include/soem/osal_defs.h | 46 - .../x86/third_party/soem/include/soem/soem.h | 28 - dependency/x86/third_party/soem/lib/libsoem.a | Bin 691060 -> 0 bytes protos/cmvr/api/agv_command.proto | 906 ------ protos/cmvr/api/agv_service.proto | 74 - .../cmvr/config/agv_config/agv_config.proto | 20 - .../biohead_config/biohead_config.proto | 44 - .../monitor_config/monitor_config.proto | 22 - .../config/robot_config/robot_config.proto | 88 - .../config/server_config/server_config.proto | 10 - request.txt | 1 - 89 files changed, 123 insertions(+), 12838 deletions(-) delete mode 100644 cmvr-es/common/config/agv_config/agv_config.pb.txt delete mode 100644 cmvr-es/common/config/biohead_config/biohead_config.pb.txt delete mode 100644 cmvr-es/common/config/monitor_config/monitor_config.pb.txt delete mode 100644 cmvr-es/common/config/robot_config/robot_config.pb.txt delete mode 100644 cmvr-es/common/config/server_config/server_config.pb.txt delete mode 100644 cmvr-es/common/utils/ffmpeg/include/FrameData.h delete mode 100644 cmvr-es/devices/agv/CMakeLists.txt delete mode 100644 cmvr-es/devices/agv/agv_src1100/CMakeLists.txt delete mode 100644 cmvr-es/devices/agv/agv_src1100/include/agv_src1100.h delete mode 100644 cmvr-es/devices/agv/agv_src1100/src/agv_src1100.cpp delete mode 100644 cmvr-es/devices/motor/eyou_motor/CMakeLists.txt delete mode 100644 cmvr-es/devices/motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.cpp delete mode 100644 cmvr-es/devices/motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.h delete mode 100644 cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_master.cpp delete mode 100644 cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_master.h delete mode 100644 cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_pdo_config.h delete mode 100644 cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_types.h delete mode 100644 cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_xml_parser.cpp delete mode 100644 cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_xml_parser.h delete mode 100644 cmvr-es/devices/motor/eyou_motor/eyou_motor.cpp delete mode 100644 cmvr-es/devices/motor/eyou_motor/eyou_motor.h delete mode 100644 cmvr-es/service/grpc/include/grpc_agv_service.h delete mode 100644 cmvr-es/service/grpc/src/grpc_agv_service.cpp delete mode 100644 dependency/x86/third_party/soem/include/soem/ec_base.h delete mode 100644 dependency/x86/third_party/soem/include/soem/ec_coe.h delete mode 100644 dependency/x86/third_party/soem/include/soem/ec_config.h delete mode 100644 dependency/x86/third_party/soem/include/soem/ec_dc.h delete mode 100644 dependency/x86/third_party/soem/include/soem/ec_eoe.h delete mode 100644 dependency/x86/third_party/soem/include/soem/ec_foe.h delete mode 100644 dependency/x86/third_party/soem/include/soem/ec_main.h delete mode 100644 dependency/x86/third_party/soem/include/soem/ec_options.h delete mode 100644 dependency/x86/third_party/soem/include/soem/ec_print.h delete mode 100644 dependency/x86/third_party/soem/include/soem/ec_soe.h delete mode 100644 dependency/x86/third_party/soem/include/soem/ec_type.h delete mode 100644 dependency/x86/third_party/soem/include/soem/nicdrv.h delete mode 100644 dependency/x86/third_party/soem/include/soem/osal.h delete mode 100644 dependency/x86/third_party/soem/include/soem/osal_defs.h delete mode 100644 dependency/x86/third_party/soem/include/soem/soem.h delete mode 100644 dependency/x86/third_party/soem/lib/libsoem.a delete mode 100644 protos/cmvr/api/agv_command.proto delete mode 100644 protos/cmvr/api/agv_service.proto delete mode 100644 protos/cmvr/config/agv_config/agv_config.proto delete mode 100644 protos/cmvr/config/biohead_config/biohead_config.proto delete mode 100644 protos/cmvr/config/monitor_config/monitor_config.proto delete mode 100644 protos/cmvr/config/robot_config/robot_config.proto delete mode 100644 protos/cmvr/config/server_config/server_config.proto diff --git a/CMakeLists.txt b/CMakeLists.txt index 77cc732c..9e100d3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,8 +122,6 @@ target_link_libraries(cmvr_es PRIVATE cmvr_es::device::humanoid_robot cmvr_es::common cmvr_es::applications - cmvr_es::device::agv_src1100 - ) install(TARGETS cmvr_es RUNTIME DESTINATION bin) diff --git a/cmvr-es/common/config/agv_config/agv_config.pb.txt b/cmvr-es/common/config/agv_config/agv_config.pb.txt deleted file mode 100644 index e9c35fe1..00000000 --- a/cmvr-es/common/config/agv_config/agv_config.pb.txt +++ /dev/null @@ -1,30 +0,0 @@ -src1100_agvs { - id: "agv_src1100" - ip: "192.168.192.5" - port_status: 19204 - port_control: 19205 - port_nav: 19206 - port_config: 19207 - port_other: 19210 - port_push: 19302 - - - - enable: true -} - - -src1100_agvs { - id: "agv_src2200" - ip: "192.168.192.5" - port_status: 19204 - port_control: 19205 - port_nav: 19206 - port_config: 19207 - port_other: 19210 - port_push: 19301 - - - - enable: true -} diff --git a/cmvr-es/common/config/biohead_config/biohead_config.pb.txt b/cmvr-es/common/config/biohead_config/biohead_config.pb.txt deleted file mode 100644 index b2880e64..00000000 --- a/cmvr-es/common/config/biohead_config/biohead_config.pb.txt +++ /dev/null @@ -1,27 +0,0 @@ -bioheads { - id: "bio_head" - serial: "/dev/ttyUSB0" - ctrlFreq: 50 - enable: false - - eyebrow { - serial: "64:0~3" - offest: "90 90 90 90" - jLmtUp: "90 170 155 110" - jLmtLow: "20 77 90 20" - } - - eye { - serial: "64:4~9" - offest: "90 90 90 90 90 90" - jLmtUp: "90 150 165 90 120 115" - jLmtLow: "20 90 90 25 70 75" - } - - mouth { - serial: "65:0~9" - offest: "90 90 90 90 90 90 90 90 90 90" - jLmtUp: "150 110 130 140 100 105 110 125 90 95" - jLmtLow: "70 30 80 80 65 55 45 80 85 90" - } -} \ No newline at end of file diff --git a/cmvr-es/common/config/camera_config/camera_config.pb.txt b/cmvr-es/common/config/camera_config/camera_config.pb.txt index f35f4018..18b02c30 100644 --- a/cmvr-es/common/config/camera_config/camera_config.pb.txt +++ b/cmvr-es/common/config/camera_config/camera_config.pb.txt @@ -27,7 +27,7 @@ realsense_cameras { align_mode: ALIGN_MODE_COLOR buffer_size: 30 sync: false - enable: false + enable: true } realsense_cameras { diff --git a/cmvr-es/common/config/dexhand_config/dexhand_config.pb.txt b/cmvr-es/common/config/dexhand_config/dexhand_config.pb.txt index 4a38bd2f..e6783dd2 100644 --- a/cmvr-es/common/config/dexhand_config/dexhand_config.pb.txt +++ b/cmvr-es/common/config/dexhand_config/dexhand_config.pb.txt @@ -1,6 +1,6 @@ rh56dftp_dexhands { id: "hand1" - ip: "192.168.1.223" + ip: "192.168.1.213" port: 6000 poll_interval_ms: 10 enable: false @@ -11,7 +11,7 @@ rh56dftp_dexhands { ip: "192.168.1.224" port: 6000 poll_interval_ms: 10 - enable: false + enable: true } px_6ax_gen3 { @@ -32,5 +32,5 @@ px_6ax_gen3 { sensor_name: "Paxini Gen3末端压力" polling_read_mode: PX_6AX_GEN3_POLLING_READ_MODE_RESULTANT_FORCE auto_calibrate: false - enable: false + enable: true } diff --git a/cmvr-es/common/config/monitor_config/monitor_config.pb.txt b/cmvr-es/common/config/monitor_config/monitor_config.pb.txt deleted file mode 100644 index d5350f3c..00000000 --- a/cmvr-es/common/config/monitor_config/monitor_config.pb.txt +++ /dev/null @@ -1,25 +0,0 @@ -# 监控管理器配置 -monitor_manager { - disk_monitor { - id: "file_monitor" - freq: 1 - enable: false - - folders { - fileDir: "/home/share/assets/audio" - maxVolume: 1000 - } - folders { - fileDir: "/home/share/assets/image" - maxVolume: 1000 - } - folders { - fileDir: "/home/share/assets/video" - maxVolume: 1000 - } - folders { - fileDir: "../log" - maxVolume: 1000 - } - } -} \ No newline at end of file diff --git a/cmvr-es/common/config/robot_config/robot_config.pb.txt b/cmvr-es/common/config/robot_config/robot_config.pb.txt deleted file mode 100644 index 9e2a9327..00000000 --- a/cmvr-es/common/config/robot_config/robot_config.pb.txt +++ /dev/null @@ -1,36 +0,0 @@ -ethercat_robots { - id: "ec_robot_01" - dof: 6 - urdf: "/home/cmvr/Projects/cmvr-es/model/xiaoyan_description/dual_arm.urdf" - baseLink: "PELVIS_S" - jointNames: "L_SHOULDER_P,L_SHOULDER_R,L_SHOULDER_Y,L_ELBOW_R,L_WRIST_P,L_WRIST_Y,L_WRIST_R,R_SHOULDER_P,R_SHOULDER_R,R_SHOULDER_Y,R_ELBOW_R,R_WRIST_P,R_WRIST_Y,R_WRIST_R" - linkNames: "PELVIS_S,L_SHOULDER_P_S,L_SHOULDER_R_S,L_SHOULDER_Y_S,L_ELBOW_R_S,L_WRIST_P_S,L_WRIST_Y_S,L_WRIST_R_S,R_SHOULDER_P_S,R_SHOULDER_R_S,R_SHOULDER_Y_S,R_ELBOW_R_S,R_WRIST_P_S,R_WRIST_Y_S,R_WRIST_R_S,R_FINGER_TIP,R_CAM" - bufferSize: 100 - verbose: true - updFreq: 1000 - enable: true - - # EtherCAT主站配置 - master { - interface: "enp1s0" - cycle_time_us: 1000 - enable: true - } - - # 电机配置列表 - motors { - slave: 1 - jointName: "L_SHOULDER_P" - limitQLb: -3.14 - limitQUb: 3.14 - limitQd: 3.0 - } - - motors { - slave: 2 - jointName: "L_SHOULDER_R" - limitQLb: -3.14 - limitQUb: 3.14 - limitQd: 3.0 - } -} \ No newline at end of file diff --git a/cmvr-es/common/config/server_config/server_config.pb.txt b/cmvr-es/common/config/server_config/server_config.pb.txt deleted file mode 100644 index 78599a06..00000000 --- a/cmvr-es/common/config/server_config/server_config.pb.txt +++ /dev/null @@ -1,3 +0,0 @@ -grpc_server { - port: "50052" -} \ No newline at end of file diff --git a/cmvr-es/common/utils/config_helper/include/config_helper.h b/cmvr-es/common/utils/config_helper/include/config_helper.h index e7ae3404..7d189128 100644 --- a/cmvr-es/common/utils/config_helper/include/config_helper.h +++ b/cmvr-es/common/utils/config_helper/include/config_helper.h @@ -10,10 +10,6 @@ #include "cmvr/config/microphone_config/microphone_config.pb.h" #include "cmvr/config/speaker_config/speaker_conifg.pb.h" #include "cmvr/config/touch_screen_app_config/touch_screen_app_config.pb.h" -#include "cmvr/config/agv_config/agv_config.grpc.pb.h" -#include "cmvr/config/biohead_config/biohead_config.pb.h" -#include "cmvr/config/robot_config/robot_config.pb.h" -#include "cmvr/config/server_config/server_config.pb.h" #define GET_CONFIG(file, para) \ ([&]() -> bool { \ @@ -86,26 +82,6 @@ namespace cmvr return SET_CONFIG(config, touch_screen_app_config_file); } - static bool getAgvConfig(config::AGVConfig& config) - { - return GET_CONFIG(agv_config_file, config); - } - - - static bool getHeadConfig(config::HeadConfig& config) - { - return GET_CONFIG(biohead_config_file, config); - } - - static bool getRobotConfig(config::RobotConfig& config) - { - return GET_CONFIG(robot_config_file, config); - } - - static bool getServerConfig(config::ServerConfig& config) - { - return GET_CONFIG(server_config_file, config); - } private: // Make macros able to call these (macros call ::cmvr::ConfigHelper::xxx) template diff --git a/cmvr-es/common/utils/config_helper/include/config_setting.h b/cmvr-es/common/utils/config_helper/include/config_setting.h index 82c22b5f..5d58fb9e 100644 --- a/cmvr-es/common/utils/config_helper/include/config_setting.h +++ b/cmvr-es/common/utils/config_helper/include/config_setting.h @@ -6,8 +6,3 @@ DECLARE_string(dexhand_config_file); DECLARE_string(microphone_config_file); DECLARE_string(speaker_config_file); DECLARE_string(touch_screen_app_config_file); -DECLARE_string(agv_config_file); - -DECLARE_string(biohead_config_file); -DECLARE_string(robot_config_file); -DECLARE_string(server_config_file); diff --git a/cmvr-es/common/utils/config_helper/src/config_setting.cpp b/cmvr-es/common/utils/config_helper/src/config_setting.cpp index 041b4885..9e44092d 100644 --- a/cmvr-es/common/utils/config_helper/src/config_setting.cpp +++ b/cmvr-es/common/utils/config_helper/src/config_setting.cpp @@ -88,19 +88,3 @@ DEFINE_string(speaker_config_file, DEFINE_string(touch_screen_app_config_file, basePath() + "touch_screen_app_config/touch_screen_app_config.pb.txt", "The configuration file for TouchScreenApp"); - -DEFINE_string(agv_config_file, - basePath() + "agv_config/agv_config.pb.txt", - "AGV config file"); - -DEFINE_string(biohead_config_file, - basePath() + "biohead_config/biohead_config.pb.txt", - "The configuration file for BioHead"); - -DEFINE_string(robot_config_file, - basePath() + "robot_config/robot_config.pb.txt", - "The configuration file for Robot"); - -DEFINE_string(server_config_file, - basePath() + "server_config/server_config.pb.txt", - "The configuration file for Server"); diff --git a/cmvr-es/common/utils/ffmpeg/include/FrameData.h b/cmvr-es/common/utils/ffmpeg/include/FrameData.h deleted file mode 100644 index cd69682a..00000000 --- a/cmvr-es/common/utils/ffmpeg/include/FrameData.h +++ /dev/null @@ -1,53 +0,0 @@ -// FrameData.h -#pragma once - -#include - -extern "C" { -#include -#include -} - -namespace ffmpeg { - - struct FrameData { - AVFrame* av_frame = nullptr; // 编码用帧(YUV格式) - cv::Mat rgb_image; // OpenCV用图像(BGR格式) - int64_t pts = 0; // 时间戳 - int64_t frame_index = 0; // 帧序号 - - FrameData() = default; - - ~FrameData() { - if (av_frame) { - av_frame_free(&av_frame); - } - } - - // 禁止拷贝 - FrameData(const FrameData&) = delete; - FrameData& operator=(const FrameData&) = delete; - - // 允许移动 - FrameData(FrameData&& other) noexcept - : av_frame(other.av_frame), - rgb_image(std::move(other.rgb_image)), - pts(other.pts), - frame_index(other.frame_index) { - other.av_frame = nullptr; - } - - FrameData& operator=(FrameData&& other) noexcept { - if (this != &other) { - if (av_frame) av_frame_free(&av_frame); - av_frame = other.av_frame; - rgb_image = std::move(other.rgb_image); - pts = other.pts; - frame_index = other.frame_index; - other.av_frame = nullptr; - } - return *this; - } - }; - -} // namespace ffmpeg \ No newline at end of file diff --git a/cmvr-es/data_center/CMakeLists.txt b/cmvr-es/data_center/CMakeLists.txt index ff66818d..d0fda580 100644 --- a/cmvr-es/data_center/CMakeLists.txt +++ b/cmvr-es/data_center/CMakeLists.txt @@ -11,7 +11,6 @@ target_link_libraries(data_center PRIVATE cmvr_es::utils cmvr_es::device::canbus cmvr_es::device::ti5motor - cmvr_es::device::eyoumotor protobuf glog ) diff --git a/cmvr-es/data_center/include/motors_info.h b/cmvr-es/data_center/include/motors_info.h index ac892cfc..56c2de04 100644 --- a/cmvr-es/data_center/include/motors_info.h +++ b/cmvr-es/data_center/include/motors_info.h @@ -23,9 +23,8 @@ namespace cmvr { public: ~MotorsInfo(){}; + void init(const XmlNode &cfg); - void init(const cmvr::config::HumanRobotConfig& config); - void init(const cmvr::config::EthercatRobotConfig& config); static MotorsInfo *getInstance() { static MotorsInfo instance; diff --git a/cmvr-es/data_center/src/motors_info.cpp b/cmvr-es/data_center/src/motors_info.cpp index 7cfa7855..4b6a087c 100644 --- a/cmvr-es/data_center/src/motors_info.cpp +++ b/cmvr-es/data_center/src/motors_info.cpp @@ -3,8 +3,6 @@ // #include "data_center/include/motors_info.h" - -#include "motor/eyou_motor/eyou_motor.h" #include "motor/ti5_motor/canopen/ti5_motor_canopen_protocol.h" #include "motor/ti5_motor/ti5_motor.h" @@ -96,10 +94,7 @@ void MotorsInfo::init(const XmlNode &cfg) { }; // 创建 MotorManager - if (!motor_manager_) - { - motor_manager_ = std::make_shared(); - } + motor_manager_ = std::make_shared(); std::vector > tasks; @@ -156,155 +151,6 @@ void MotorsInfo::init(const XmlNode &cfg) { LOG(INFO) << "All enabled motors initialized successfully."; } -void MotorsInfo::init(const cmvr::config::HumanRobotConfig& config) { - const auto& can_manager = config.can_manager(); - - // 读取enable状态 - left_arm_enabled_ = can_manager.left_arm().enable(); - right_arm_enabled_ = can_manager.right_arm().enable(); - waist_enabled_ = can_manager.waist().enable(); - head_enabled_ = can_manager.head().enable(); - - // 局部变量存储电机配置 - std::vector l_motors_cfg; - std::vector r_motors_cfg; - std::vector waist_motors_cfg; - std::vector head_motors_cfg; - - // 左臂 - if (left_arm_enabled_) { - const auto& left_arm = can_manager.left_arm(); - for (const auto& motor : left_arm.motors()) { - l_motors_cfg.push_back(motor); - } - l_can_client_ = std::make_shared(left_arm); - l_can_sender_ = std::make_shared>(); - l_can_receiver_ = std::make_shared>(); - l_message_manager_ = std::make_shared>(); - } - - // 右臂 - if (right_arm_enabled_) { - const auto& right_arm = can_manager.right_arm(); - for (const auto& motor : right_arm.motors()) { - r_motors_cfg.push_back(motor); - } - r_can_client_ = std::make_shared(right_arm); - r_can_sender_ = std::make_shared>(); - r_can_receiver_ = std::make_shared>(); - r_message_manager_ = std::make_shared>(); - } - - // 腰部 - if (waist_enabled_) { - const auto& waist = can_manager.waist(); - for (const auto& motor : waist.motors()) { - waist_motors_cfg.push_back(motor); - } - waist_can_client_ = std::make_shared(waist); - waist_can_sender_ = std::make_shared>(); - waist_can_receiver_ = std::make_shared>(); - waist_message_manager_ = std::make_shared>(); - } - - // 头部 - if (head_enabled_) { - const auto& head = can_manager.head(); - for (const auto& motor : head.motors()) { - head_motors_cfg.push_back(motor); - } - head_can_client_ = std::make_shared(head); - head_can_sender_ = std::make_shared>(); - head_can_receiver_ = std::make_shared>(); - head_message_manager_ = std::make_shared>(); - } - - // 初始化 limbs - struct Limb { - std::string name; - bool enabled; - std::shared_ptr client; - std::shared_ptr> sender; - std::shared_ptr> receiver; - std::shared_ptr> message_manager; - std::vector motor_cfgs; - }; - - std::vector limbs{ - {"WAIST", waist_enabled_, waist_can_client_, waist_can_sender_, waist_can_receiver_, waist_message_manager_, waist_motors_cfg}, - {"LEFT_ARM", left_arm_enabled_, l_can_client_, l_can_sender_, l_can_receiver_, l_message_manager_, l_motors_cfg}, - {"RIGHT_ARM", right_arm_enabled_, r_can_client_, r_can_sender_, r_can_receiver_, r_message_manager_, r_motors_cfg}, - {"HEAD", head_enabled_, head_can_client_, head_can_sender_, head_can_receiver_, head_message_manager_, head_motors_cfg} - }; - - if (!motor_manager_) - { - motor_manager_ = std::make_shared(); - } - std::vector> tasks; - - for (auto &limb : limbs) { - if (!limb.enabled) continue; - - if (limb.client) limb.client->init(); - - if (limb.sender && limb.receiver && limb.client) { - auto ret = limb.sender->Init(limb.client.get(), false); - if (ret != ErrorCode::OK) - LOG(ERROR) << "Failed to init " << limb.name << " CAN sender."; - - ret = limb.receiver->Init(limb.client.get(), limb.message_manager.get(), false); - if (ret != ErrorCode::OK) - LOG(ERROR) << "Failed to init " << limb.name << " CAN receiver."; - - limb.client->start(); - ret = limb.sender->Start(); - if (ret != ErrorCode::OK) - LOG(ERROR) << "Failed to start " << limb.name << " CAN sender."; - - ret = limb.receiver->Start(); - if (ret != ErrorCode::OK) - LOG(ERROR) << "Failed to start " << limb.name << " CAN receiver."; - } - - std::shared_ptr protocol = nullptr; - if (limb.sender && limb.message_manager) { - protocol = std::make_shared(limb.sender, limb.message_manager); - } - - if (!limb.motor_cfgs.empty()) { - tasks.push_back(std::async(std::launch::async, [this, protocol, &limb] { - LOG(INFO) << "[Thread " << std::this_thread::get_id() << "] Start initializing " << limb.name << " motors..."; - for (const auto& motor_cfg : limb.motor_cfgs) { - auto motor = std::make_shared(motor_cfg); - if (protocol) motor->setProtocol(protocol); - motor->init(); - motor_manager_->addMotor(motor); - } - })); - } - } - - for (auto& task : tasks) task.get(); - LOG(INFO) << "All enabled motors initialized successfully."; -} - - -void MotorsInfo::init(const cmvr::config::EthercatRobotConfig& config) -{ - if (!motor_manager_) - { - motor_manager_ = std::make_shared(); - } - auto protocol = std::make_shared(config.master().interface()); - for (const auto& motor_cfg : config.motors()) { - auto motor = std::make_shared(motor_cfg); - if (protocol) motor->setProtocol(protocol); - motor->init(); - motor_manager_->addMotor(motor); - } -} - bool MotorsInfo::getJointsQ(const std::vector &joints_name, std::unordered_map &joint_qs) const { auto res{true}; diff --git a/cmvr-es/device_manager/CMakeLists.txt b/cmvr-es/device_manager/CMakeLists.txt index d74d86c3..0b97b456 100644 --- a/cmvr-es/device_manager/CMakeLists.txt +++ b/cmvr-es/device_manager/CMakeLists.txt @@ -15,7 +15,6 @@ target_link_libraries(device_manager PRIVATE cmvr_es::device::rh56dftp_dexhand cmvr_es::device::px_6ax_gen3 cmvr::device::head_esp32 - cmvr_es::device::agv_src1100 osqp cmvr_es::device::humanoid_robot # cmvr_es::device::aubo_robot diff --git a/cmvr-es/device_manager/include/device_factory.h b/cmvr-es/device_manager/include/device_factory.h index b46e3495..1fcce739 100644 --- a/cmvr-es/device_manager/include/device_factory.h +++ b/cmvr-es/device_manager/include/device_factory.h @@ -28,7 +28,7 @@ namespace cmvr::device { template std::shared_ptr createFromConfig(const ConfigType& cfg); private: - std::shared_ptr create_agv_(const XmlNode& cfg); + std::shared_ptr create_agv_(const XmlNode& cfg); std::shared_ptr create_battery_(const XmlNode& cfg); std::shared_ptr create_camera_(const XmlNode& cfg); std::shared_ptr create_dexhand_(const XmlNode& cfg); diff --git a/cmvr-es/device_manager/include/device_manager.h b/cmvr-es/device_manager/include/device_manager.h index 8f54823b..ee782e5d 100644 --- a/cmvr-es/device_manager/include/device_manager.h +++ b/cmvr-es/device_manager/include/device_manager.h @@ -16,7 +16,7 @@ namespace cmvr::device { using DeviceVariant = std::variant< - std::shared_ptr, + std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, diff --git a/cmvr-es/device_manager/src/device_factory.cpp b/cmvr-es/device_manager/src/device_factory.cpp index 84262f35..fba92e51 100644 --- a/cmvr-es/device_manager/src/device_factory.cpp +++ b/cmvr-es/device_manager/src/device_factory.cpp @@ -15,13 +15,12 @@ //#include "robot/ti5_robot/ti5_robot.h" #include "cmvr/api/system_command.pb.h" #include "data_center/include/motors_info.h" -#include "devices/agv/agv_src1100/include/agv_src1100.h" //#include "devices/robot/aubo_robot/include/aubo_robot.h" using namespace std; using namespace cmvr::device; -template std::shared_ptr DeviceFactory::create(const XmlNode&); +template std::shared_ptr DeviceFactory::create(const XmlNode&); template std::shared_ptr DeviceFactory::create(const XmlNode&); template std::shared_ptr DeviceFactory::create(const XmlNode&); template std::shared_ptr DeviceFactory::create(const XmlNode&); @@ -33,7 +32,7 @@ template std::shared_ptr DeviceFactory::create template std::shared_ptr DeviceFactory::create(const XmlNode& cfg) { - if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) { return create_agv_(cfg); } else if constexpr (std::is_same_v) { return create_battery_(cfg); @@ -58,7 +57,7 @@ std::shared_ptr DeviceFactory::create(const XmlNode& cfg) { } } -std::shared_ptr DeviceFactory::create_agv_(const XmlNode& cfg) { +std::shared_ptr DeviceFactory::create_agv_(const XmlNode& cfg) { return nullptr; } @@ -185,11 +184,6 @@ template std::shared_ptr DeviceFactory::createFromConfig DeviceFactory::createFromConfig(const config::PX6AXGen3& cfg); template std::shared_ptr DeviceFactory::createFromConfig(const config::FFMpegMicroPhoneConfig& cfg); template std::shared_ptr DeviceFactory::createFromConfig(const config::FFMpegSpeakerConfig& cfg); -template std::shared_ptr DeviceFactory::createFromConfig(const config::AGVsrc1100Config& cfg); -template std::shared_ptr DeviceFactory::createFromConfig(const config::BioHeadConfig& cfg); -template std::shared_ptr DeviceFactory::createFromConfig(const config::HumanRobotConfig& cfg); -template std::shared_ptr DeviceFactory::createFromConfig(const config::EthercatRobotConfig& cfg); - template std::shared_ptr DeviceFactory::createFromConfig(const ConfigType& cfg) { @@ -250,50 +244,6 @@ std::shared_ptr DeviceFactory::createFromConfig(const ConfigType& cf throw runtime_error("[DeviceFactory]: Unsupported microphone device type"); } } - else if constexpr (std::is_same_v) - { - if constexpr (std::is_same_v) - { - return std::make_shared(cfg); - } - else - { - LOG(ERROR) << "[DeviceFactory]: Unsupported microphone device type "; - throw runtime_error("[DeviceFactory]: Unsupported microphone device type"); - } - } - else if constexpr (std::is_same_v) - { - if constexpr (std::is_same_v) - { - auto motos_info = MotorsInfo::getInstance(); - motos_info->init(cfg); - return std::make_shared>(cfg); - } - else if constexpr (std::is_same_v) - { - auto motos_info = MotorsInfo::getInstance(); - motos_info->init(cfg); - return std::make_shared>(cfg); - } - else - { - LOG(ERROR) << "[DeviceFactory]: Unsupported microphone device type "; - throw runtime_error("[DeviceFactory]: Unsupported microphone device type"); - } - } - else if constexpr (std::is_same_v) - { - if constexpr (std::is_same_v) - { - return std::make_shared(cfg); - } - else - { - LOG(ERROR) << "[DeviceFactory]: Unsupported agv device type "; - throw runtime_error("[DeviceFactory]: Unsupported agv device type"); - } - } else { LOG(ERROR) << "[DeviceFactory]: Unsupported device type"; throw runtime_error("[DeviceFactory]: Unsupported device type"); diff --git a/cmvr-es/device_manager/src/device_manager.cpp b/cmvr-es/device_manager/src/device_manager.cpp index 60a8c824..805516f9 100644 --- a/cmvr-es/device_manager/src/device_manager.cpp +++ b/cmvr-es/device_manager/src/device_manager.cpp @@ -9,7 +9,7 @@ using namespace std; using namespace cmvr::device; using namespace cmvr::device; -template std::shared_ptr DeviceManager::getDevice(const std::string& device_id); +template std::shared_ptr DeviceManager::getDevice(const std::string& device_id); template std::shared_ptr DeviceManager::getDevice(const std::string& device_id); template std::shared_ptr DeviceManager::getDevice(const std::string& device_id); template std::shared_ptr DeviceManager::getDevice(const std::string& device_id); @@ -152,7 +152,7 @@ void DeviceManager::getDeviceList(std::list> string dev_type; std::visit([&](const auto& ptr) { using T = std::decay_t; - if constexpr (std::is_same_v>) { + if constexpr (std::is_same_v>) { dev_type = "AGV"; } else if constexpr (std::is_same_v>) { dev_type = "Battery"; @@ -223,12 +223,12 @@ void DeviceManager::init_devices_() { LOG(ERROR) << "[DeviceManager]: Duplicate AGV Device ID" << id; throw runtime_error("[DeviceManager]: Duplicate AGV Device ID" + id); } - auto device = dev_factory_->create(node); + auto device = dev_factory_->create(node); if (device == nullptr) { throw std::runtime_error("[DeviceManager]: Factory returned nullptr for device ID: " + id); } devices_[id] = device; - std::get>(devices_[id])->init(); + std::get>(devices_[id])->init(); LOG(INFO) << "[DeviceManager]: Init AGV " << id << " Success"; } @@ -312,24 +312,6 @@ void DeviceManager::init_devices_() { LOG(INFO) << "[DeviceManager]: Init Robot " << id << " Success"; } - // EYOU的先用新版的 - config::RobotConfig robot_config; - ConfigHelper::getRobotConfig(robot_config); - for (int i = 0; i < robot_config.ethercat_robots().size(); i++) { - auto robot = robot_config.ethercat_robots(i); - if (!robot.enable()) - continue; - auto id = robot.id(); - if (devices_.count(id)) { - LOG(ERROR) << "[DeviceManager]: Duplicate Robot Device ID" << id; - throw runtime_error("[DeviceManager]: Duplicate Robot Device ID" + id); - } - std::cout << "DexHand[" << i << "]: " << id << std::endl; - auto device = dev_factory_->createFromConfig(robot); - devices_[id] = device; - std::get>(devices_[id])->init(); - } - config::MicroPhoneConfig micro_phone_config; ConfigHelper::getMicroPhonesConfig(micro_phone_config); for (int i = 0; i < micro_phone_config.ffmpeg_microphones().size(); i++) { @@ -364,38 +346,22 @@ void DeviceManager::init_devices_() { std::get>(devices_[id])->init(); } - config::HeadConfig head_cfg; - ConfigHelper::getHeadConfig(head_cfg); - for (int i = 0; i < head_cfg.bioheads().size(); i++) { - auto head = head_cfg.bioheads(i); - if (!head.enable()) - continue; - auto id = head.id(); - if (devices_.count(id)) { - LOG(ERROR) << "[DeviceManager]: Duplicate Biohead Device ID" << id; - throw runtime_error("[DeviceManager]: Duplicate Biohead Device ID" + id); + + auto biohead_node = dmgr_node.getChild("BioHead"); + for (auto &node: biohead_node.getChildren()){ + string id = node.getAttrString("id"); + if (devices_.count(id)){ + LOG(ERROR) << "[DeviceManager]: Duplicate AbstractBiohead Device ID" << id; + throw runtime_error("[DeviceManager]: Duplicate AbstractBiohead Device ID" + id); + } + auto device = dev_factory_->create(node); + if (device == nullptr){ + throw std::runtime_error("[DeviceManager]: Factory returned nullptr for device ID: " + id); } - std::cout << "Biohead[" << i << "]: " << id << std::endl; - auto device = dev_factory_->createFromConfig(head); devices_[id] = device; std::get>(devices_[id])->init(); - } - config::AGVConfig agv_config; - ConfigHelper::getAgvConfig(agv_config); + LOG(INFO) << "[BioHead]: Init biohead " << id << " Success"; - for (int i = 0; i < agv_config.src1100_agvs().size(); i++) { - auto agv = agv_config.src1100_agvs(i); - if (!agv.enable()) - continue; - auto id = agv.id(); - if (devices_.count(id)) { - LOG(ERROR) << "[DeviceManager]: Duplicate agv Device ID" << id; - throw runtime_error("[DeviceManager]: Duplicate agv Device ID" + id); - } - std::cout << "AgvSrc1100[" << i << "]: " << id << std::endl; - auto device = dev_factory_->createFromConfig(agv); - devices_[id] = device; - std::get>(devices_[id])->init(); } } catch (const exception& e) { diff --git a/cmvr-es/devices/CMakeLists.txt b/cmvr-es/devices/CMakeLists.txt index 21191941..41c64db3 100644 --- a/cmvr-es/devices/CMakeLists.txt +++ b/cmvr-es/devices/CMakeLists.txt @@ -6,4 +6,3 @@ add_subdirectory(biohead) add_subdirectory(robot) add_subdirectory(canbus) add_subdirectory(motor) -add_subdirectory(agv) diff --git a/cmvr-es/devices/agv/CMakeLists.txt b/cmvr-es/devices/agv/CMakeLists.txt deleted file mode 100644 index 0d8b4bb9..00000000 --- a/cmvr-es/devices/agv/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_subdirectory(agv_src1100) - diff --git a/cmvr-es/devices/agv/abstract_agv.h b/cmvr-es/devices/agv/abstract_agv.h index 858e3973..a838fa89 100644 --- a/cmvr-es/devices/agv/abstract_agv.h +++ b/cmvr-es/devices/agv/abstract_agv.h @@ -1,869 +1,36 @@ // -// Created by xtkuang on 2025/5/6. +// Created by xtkuang on 2025/5/8. // -/** - * @file abstract_agv.h - * @brief AGV设备抽象基类定义,提供统一的接口规范和数据结构。 - * 所有具体AGV实现(如SRC-1100)需继承此类。 - * @note 本文件定义了大量与仙工API对应的结构体,用于状态查询、运动控制、导航任务等。 - */ + #ifndef CMVR_ES_ABSTRACT_AGV_H #define CMVR_ES_ABSTRACT_AGV_H #pragma once -#include -#include -#include -#include -#include -#include -#include -#include +#include "../abstract_device.h" -#include "devices/abstract_device.h" +namespace cmvr::device{ + class AbstractAGV: public AbstractDevice { + public: + explicit AbstractAGV(const XmlNode &config): AbstractDevice(config) {}; + ~AbstractAGV() override=default; -// 注意:这里只include,不使用 -#include "cmvr/config/agv_config/agv_config.pb.h" + virtual void getState(AGVState &state) {} -namespace cmvr::device { + // navigation + virtual void eStop() {} + virtual void goHome() {} + virtual void moveto(math::Pose2d &location, double speed_ratio) {} + virtual void setVelocity(math::Vec3 linear, math::Vec3 angular) {} -/** - * @brief AGV(自动导引车)设备的抽象基类 - * - * 继承自 AbstractDevice,定义了 AGV 设备通用的接口和行为, - * 包括状态管理、信息查询、运动控制、地图管理、导航任务等功能。 - * 所有具体 AGV 设备实现必须继承此类并实现其纯虚函数。 - */ -class AbstractAgv : public AbstractDevice { -public: - /** - * @brief AGV 设备运行状态枚举 - */ - enum class Status { - CREATED, ///< 已创建,未初始化 - INITIALIZED, ///< 已初始化,未运行 - RUNNING, ///< 运行中 - PAUSED, ///< 暂停 - STOPPED, ///< 已停止 - FAULT ///< 故障 + // map + virtual void initMap(float resolution, int width, int height) {} + virtual void updateMap() {} + virtual void saveMap(const std::string& file_path) {} + virtual void loadMap(const std::string& file_path) {} + + protected: + AGVState state_; }; +} - // ==================== 状态与信息结构体 ==================== - - /** - * @brief 机器人基本信息查询结果 - * - * 对应仙工 API 1000 (0x03E8) 的响应字段 - */ - struct AgvStatusInfo { - std::string id; ///< 设备唯一标识 - std::string vehicle_id; ///< 车辆编号(如 "agv_001") - std::string robot_note; ///< 机器人备注(用户自定义) - std::string version; ///< 软件版本号 - std::string model; ///< 车型(如 "SRC-1100") - std::string dsp_version; ///< DSP 固件版本 - std::string gyro_version; ///< 陀螺仪版本 - std::string map_version; ///< 地图版本 - std::string model_version; ///< 模型版本 - std::string netprotocol_version; ///< 网络协议版本 - std::string modbus_version; ///< Modbus 协议版本 - std::string current_map; ///< 当前加载的地图名称 - std::string current_map_md5; ///< 当前地图的 MD5 校验值 - std::string model_md5; ///< 模型文件 MD5 - std::string ssid; ///< 当前连接的 Wi-Fi SSID - int rssi = 0; ///< Wi-Fi 信号强度 (RSSI, 0~100) - std::string ap_addr; ///< 接入点 MAC 地址 - std::string current_ip; ///< 当前 IP 地址 - std::string mac; ///< 设备 MAC 地址 - std::string echoid_type; ///< 回音 ID 类型 - std::string echoid; ///< 回音 ID - int ret_code = 0; ///< 返回码,0 表示成功,非0 表示错误 - std::string err_msg; ///< 错误信息(失败时填充) - /** - * @brief 检查信息是否有效(ID 非空且返回码为 0) - */ - bool isValid() const { - return !id.empty() && ret_code == 0; - } - }; - - /** - * @brief 电池状态信息 - * - * 对应仙工 API 1007 (0x03EF) 的响应字段 - */ - struct BatteryStatus - { - double battery_level = 0.0; ///< 电池电量百分比 (0~100) - double battery_temp = 0.0; ///< 电池温度(摄氏度) - bool charging = false; ///< 是否正在充电 - double voltage = 0.0; ///< 当前电压(V) - double current = 0.0; ///< 当前电流(A) - double max_charge_voltage = -1.0; ///< 最大充电电压(-1 表示未知) - double max_charge_current = -1.0; ///< 最大充电电流(-1 表示未知) - bool manual_charge = false; ///< 是否连接手动充电器(仅 SRC-2000) - bool auto_charge = false; ///< 是否连接自动充电桩(仅 SRC-2000) - int32_t battery_cycle = 0; ///< 电池循环次数(从 BMS 读取,可能不准确) - std::string battery_user_data; ///< 电池用户自定义数据(字符串) - std::string extra; ///< 额外扩展字段(预留) - int32_t ret_code = 0; ///< 返回码,0 表示成功 - std::string create_on; ///< 数据创建时间戳(ISO 8601) - std::string err_msg; ///< 错误信息 - }; - - /** - * @brief 机器人位置信息 - * - * 对应仙工 API 1004 (0x03EC) 的响应字段 - */ - struct RobotLocation - { - double x = 0.0; ///< 世界坐标系 X 坐标(米) - double y = 0.0; ///< 世界坐标系 Y 坐标(米) - double angle = 0.0; ///< 朝向角(弧度),逆时针为正 - double confidence = 0.0; ///< 定位置信度(0~1) - std::string current_station; ///< 当前所在站点 ID(若在站点上) - std::string last_station; ///< 上一个经过的站点 ID - int32_t loc_method = 0; ///< 定位方法(0: 自然轮廓, 1: 反光柱, 2: 二维码, 3: 里程计, ...) - int32_t ret_code = 0; ///< 返回码,0 表示成功 - std::string create_on; ///< 数据创建时间戳 - std::string err_msg; ///< 错误信息 - }; - - /** - * @brief 地图下载结果 - * - * 对应仙工 API 4011 (0x0FAB) 的响应 - */ - struct DownloadMapResult - { - std::string map_name; ///< 地图名称 - std::string map_content; ///< 地图文件内容(JSON 字符串) - int32_t ret_code = 0; ///< 返回码,0 表示成功 - std::string create_on; ///< 数据创建时间戳 - std::string err_msg; ///< 错误信息 - }; - - /** - * @brief 单个地图文件信息 - */ - struct MapFileInfo - { - std::string name; ///< 文件名 - std::string modified; ///< 最后修改时间 - int64_t size = 0; ///< 文件大小(字节) - }; - - /** - * @brief 地图整体状态(当前地图、所有地图列表及文件详情) - * - * 对应仙工 API 1300 (0x0514) 的响应 - */ - struct MapStatus - { - std::string current_map; ///< 当前加载的地图名称 - std::string current_map_md5; ///< 当前地图的 MD5 - std::vector maps; ///< 所有地图名称列表 - std::vector map_files_info; ///< 每个地图文件的详细信息 - int32_t ret_code = 0; ///< 返回码,0 表示成功 - std::string create_on; ///< 数据创建时间戳 - std::string err_msg; ///< 错误信息 - }; - - /** - * @brief 上传地图结果 - * - * 对应仙工 API 4010 (0x0FAA) 的响应 - */ - struct UploadMapResult - { - int32_t ret_code = 0; ///< 返回码,0 表示成功 - std::string create_on; ///< 数据创建时间戳 - std::string err_msg; ///< 错误信息 - }; - - /** - * @brief 抢占控制权返回结果 - * - * 对应仙工 API 4005 (0x0FA5) 的响应 - */ - struct LockResult - { - int32_t ret_code = 0; ///< 返回码,0 表示成功 - std::string create_on; ///< 数据创建时间戳 - std::string err_msg; ///< 错误信息 - }; - - /** - * @brief 当前控制权信息 - * - * 对应仙工 API 1060 (0x0424) 的响应 - */ - struct CurrentLockStatus - { - bool locked = false; ///< 是否被锁定(有控制权持有者) - std::string ip; ///< 持有者 IP 地址 - int32_t port = 0; ///< 持有者端口 - uint8_t type = 0; ///< 持有者类型(0: 未知, 1: 调度系统, 2: 示教器, ...) - std::string nick_name; ///< 持有者昵称(自定义标识) - int64_t time_t = 0; ///< 锁定时间戳(Unix 秒) - std::string desc; ///< 描述信息 - int32_t ret_code = 0; ///< 返回码,0 表示成功 - std::string create_on; ///< 数据创建时间戳 - std::string err_msg; ///< 错误信息 - }; - - // ==================== 运动控制相关结构体 ==================== - - /** - * @brief 开环运动下发参数 - * - * 对应仙工 API 2010 (0x07DA) 的请求参数 - * 注意:多舵轮设备仅 vx/vy/w 生效,steer/real_steer 仅单舵轮设备有效。 - */ - struct MotionCtrlReq - { - double vx = 0.0; ///< 机器人坐标系 X 方向线速度(m/s) - double vy = 0.0; ///< 机器人坐标系 Y 方向线速度(m/s) - double w = 0.0; ///< 角速度(rad/s),逆时针为正 - double steer = 0.0; ///< 舵轮转向角(仅单舵轮,单位 rad) - double real_steer = 0.0; ///< 实际舵轮角度(反馈,仅单舵轮) - int64_t duration = -1; ///< 运动持续时间(毫秒),-1 表示一直运动直到下次指令 - }; - - /** - * @brief 运动控制返回结果 - */ - struct MotionCtrlRes - { - int ret_code = 0; ///< 返回码,0 表示成功 - std::string create_on; ///< 数据创建时间戳 - std::string err_msg; ///< 错误信息 - }; - - /** - * @brief 切换地图入参 - * - * 对应仙工 API 2022 (0x07E6) 的请求 - */ - struct LoadMapReq - { - std::string map_name; ///< 目标地图名称(必须已存在于机器人中) - }; - - /** - * @brief 切换地图返回结果 - */ - struct LoadMapRes - { - int ret_code = 0; ///< 返回码,0 表示成功 - std::string create_on; ///< 数据创建时间戳 - std::string err_msg; ///< 错误信息 - }; - - /** - * @brief 查询地图载入状态返回 - * - * 对应仙工 API 1022 (0x03FE) 的响应 - */ - struct QueryLoadMapStatusRes - { - int32_t loadmap_status = 0; ///< 地图加载状态:0=失败, 1=成功, 2=载入中 - int ret_code = 0; ///< 返回码,0 表示成功 - std::string create_on; ///< 数据创建时间戳 - std::string err_msg; ///< 错误信息 - }; - - /** - * @brief 单个站点信息 - * - * 对应仙工 API 1301 (0x0515) 响应中的单个站点对象 - */ - struct StationItem - { - std::string id; ///< 站点 ID(唯一标识) - std::string type; ///< 站点类型(如 "LocationMark", "ChargePoint", "ActionPoint") - double x = 0.0; ///< 站点 X 坐标(米) - double y = 0.0; ///< 站点 Y 坐标(米) - double r = 0.0; ///< 站点朝向角(弧度) - std::string desc; ///< 描述信息 - std::string executor; ///< 执行器信息(用于动作) - std::string prepoint; ///< 前置点 ID(若需要先到前置点) - std::string recfile; ///< 识别文件路径(用于视觉识别) - bool spin = false; ///< 是否允许自旋(原地旋转) - bool use_down_pgv = false; ///< 是否使用下视 PGV 二维码 - }; - - /** - * @brief 查询站点列表返回结果 - */ - struct QueryStationRes - { - std::vector stations; ///< 站点列表 - int ret_code = 0; ///< 返回码,0 表示成功 - std::string create_on; ///< 数据创建时间戳 - std::string err_msg; ///< 错误信息 - }; - - /** - * @brief 单段导航任务(用于指定路径导航) - * - * 对应仙工 API 3066 (0x0BFA) 中 move_task_list 的每个元素 - */ - struct MoveTaskItem - { - std::string task_id; ///< 任务 ID(必须全局唯一) - std::string source_id; ///< 起始站点 ID - std::string id; ///< 目标站点 ID - std::string operation; ///< 到达后执行的动作(如 "JackLoad", "WaitDI", 等) - double jack_height = 0.0; ///< 顶升高度(当 operation 为顶升相关时使用) - }; - - /** - * @brief 指定路径导航入参(多个导航段) - * - * 对应仙工 API 3066 的请求数据区 - */ - struct GoTargetListReq - { - std::vector move_task_list; ///< 导航任务序列(必须按顺序且相邻站点间有直接路径) - }; - - /** - * @brief 指定路径导航返回结果 - */ - struct GoTargetListRes - { - int ret_code = 0; ///< 返回码,0 表示成功 - std::string create_on; ///< 数据创建时间戳 - std::string err_msg; ///< 错误信息 - }; - - // ==================== 导航料箱信息 ==================== - /** - * @brief 导航任务中涉及的料箱信息 - */ - struct NavContainerItem - { - std::string container_name; ///< 料箱名称 - std::string desc; ///< 描述 - std::string goods_id; ///< 绑定的货物 ID - bool has_goods = false; ///< 是否已绑定货物 - }; - - // ==================== 1020 实时导航查询 ==================== - /** - * @brief 1020 实时导航查询入参 - * - * 对应仙工 API 1020 (0x03FC) - */ - struct RobotStatusTaskCurrentReq - { - bool simple = false; ///< 若为 true,只返回 task_status 字段 - }; - - /** - * @brief 1020 实时导航查询出参 - * - * 对应仙工 API 1020 的响应 - */ - struct RobotStatusTaskCurrentRes - { - int task_status = 0; ///< 任务状态:0=NONE, 1=WAITING, 2=RUNNING, 3=SUSPENDED, 4=COMPLETED, 5=FAILED, 6=CANCELED - int task_type = 0; ///< 任务类型:0=无, 1=自由导航到坐标, 2=自由导航到站点, 3=路径导航到站点, 7=平动转动, 100=其他 - std::string target_id; ///< 目标站点 ID(若 task_type 为 2 或 3) - std::vector target_point; ///< 目标坐标 [x, y, r](若 task_type 为 1) - std::vector finished_path; ///< 已经过的站点列表 - std::vector unfinished_path; ///< 尚未经过的站点列表 - std::string move_status_info; ///< 导航附加信息(如错误描述) - std::vector containers; ///< 料箱信息(若有) - int ret_code = 0; ///< 返回码,0 表示成功 - std::string create_on; ///< 数据创建时间戳 - std::string err_msg; ///< 错误信息 - }; - - // ==================== 1110 批量任务查询 ==================== - /** - * @brief 单条任务状态项 - */ - struct SingleTaskStatusItem - { - std::string task_id; ///< 任务 ID - int status = 0; ///< 状态码(同上述 task_status) - int type = 0; ///< 任务类型(同上述 task_type) - }; - - /** - * @brief 任务状态包(包含最近完成和所有未完成的任务) - * - * 对应仙工 API 1110 (0x0456) 的响应中的 task_status_package - */ - struct TaskStatusPackage - { - std::string closest_target; ///< 最近站点 ID - std::string source_name; ///< 起点站点名称 - std::string target_name; ///< 终点站点名称 - double percentage = 0.0; ///< 任务进度百分比(0~100) - double distance = 0.0; ///< 剩余距离(米) - std::string info; ///< 附加信息 - std::vector task_status_list; ///< 所有任务状态列表 - }; - - /** - * @brief 1110 批量查询任务入参 - */ - struct QueryTaskStatusPackageReq - { - std::vector task_ids; ///< 要查询的任务 ID 列表,若为空则查询所有未完成+最近一条完成 - }; - - /** - * @brief 1110 批量查询任务出参 - */ - struct QueryTaskStatusPackageRes - { - TaskStatusPackage task_status_package; ///< 任务状态包 - int ret_code = 0; ///< 返回码,0 表示成功 - std::string create_on; ///< 数据创建时间戳 - std::string err_msg; ///< 错误信息 - }; - - // ==================== DI / DO / 音频 / PGV / 脚本等通用类型 ==================== - /** - * @brief DI 点位信息 - */ - struct DIItem - { - int id = 0; ///< DI 编号 - bool status = false; ///< 状态(true=高电平, false=低电平) - }; - /** - * @brief DO 点位信息 - */ - struct DOItem - { - int id = 0; ///< DO 编号 - bool status = false; ///< 状态(true=高电平, false=低电平) - }; - /** - * @brief 音频播放参数 - */ - struct SoundArgs - { - std::string name; ///< 音频文件名 - int loop = 0; ///< 循环次数(0=播放一次, 1=循环播放) - int stop = 0; ///< 1=停止播放 - }; - /** - * @brief WaitDI 动作参数 - */ - struct WaitDIArgs - { - std::vector DI; ///< 需要等待的 DI 条件列表 - double timeout = 0.0; ///< 超时时间(秒),0 表示无限等待 - }; - /** - * @brief SetDO 动作参数 - */ - struct SetDOArgs - { - std::vector DO; ///< 要设置的 DO 列表 - }; - /** - * @brief PGV(二维码)二次定位参数 - */ - struct PgvParam - { - bool use_pgv = false; ///< 是否使用上视 PGV - bool use_down_pgv = false; ///< 是否使用下视 PGV - double pgv_adjust_dist = 0.0; ///< 最大调整半径(米) - double pgv_adjust_cx = 0.0; ///< 二维码坐标系下的 X 偏移(米) - double pgv_adjust_cy = 0.0; ///< 二维码坐标系下的 Y 偏移(米) - double pgv_x_adjust = 0.0; ///< 仅调整 X 方向误差(米) - }; - /** - * @brief 自由导航目标点(x, y, theta) - */ - struct FreeGoPoint - { - double x = 0.0; ///< X 坐标(米) - double y = 0.0; ///< Y 坐标(米) - double theta = 0.0; ///< 朝向角(弧度) - }; - /** - * @brief 脚本参数(通用) - */ - struct ScriptArgs - { - std::map str_kv; ///< 字符串键值对 - std::map num_kv; ///< 数字键值对 - std::vector do_list; ///< DO 列表(用于 setDO 等) - std::vector di_list; ///< DI 列表(用于 waitDI 等) - }; - - // ==================== 3051 单点导航 ==================== - /** - * @brief 3051 单点导航入参 - * - * 对应仙工 API 3051 (0x0BEB) 的请求数据区 - * - * @attention - * - **仅用于单车测试/验证,严禁用于多车调度场景**,否则会出现速度不连续、不跟随路径等危险。 - * - 下发此任务会取消当前正在执行的任务(不排队)。 - * - 成功下发后会自动清除指定的 warning/error 报错码。 - * - 支持自由导航(freeGo)和基于站点的路径导航两种模式。 - */ - struct RobotGoTargetReq - { - // -------- 必填字段 -------- - std::string source_id; ///< 起始站点 ID(若为 "SELF_POSITION" 表示从当前位置出发) - std::string id; ///< 目标站点 ID(若为 "SELF_POSITION" 表示原地执行 operation) - std::string task_id; ///< 任务 ID(非强制,但建议提供) - - // -------- 可选速度/角度控制 -------- - double angle = 0.0; ///< 目标点朝向角(弧度),仅对自由导航或目标站点有方向要求时使用 - std::string method; ///< 运动方式:"forward"(正走)或 "backward"(倒走) - double max_speed = 0.0; ///< 最大线速度(m/s),0 表示使用默认 - double max_wspeed = 0.0; ///< 最大角速度(rad/s) - double max_acc = 0.0; ///< 最大加速度(m/s²) - double max_wacc = 0.0; ///< 最大角加速度(rad/s²) - int64_t duration = 0; ///< 到达后等待时间(毫秒) - int32_t orientation = 0; ///< 全向车保持的角度(暂不使用) - bool spin = false; ///< 是否随动(用于托盘旋转) - int64_t delay = 0; ///< 延迟结束导航状态的时间(毫秒),缺省为 0 - int32_t start_rot_dir = 0; ///< 起步原地旋转方向:-1=顺时针, 0=就近, 1=逆时针 - int32_t end_rot_dir = 0; ///< 到点原地旋转方向(同上) - double reach_dist = 0.0; ///< 到点位置精度(米),默认由系统决定 - double reach_angle = 0.0; ///< 到点角度精度(弧度) - std::string skill_name; ///< 技能名称:"Action"(动作)或 "GotoSpecifiedPose"(路径导航) - - // -------- PGV 二次定位 -------- - PgvParam pgv; ///< PGV 参数 - - // -------- 设备操作(顶升/货叉/辊筒/牵引等) -------- - std::string operation; ///< 操作类型,如 "JackLoad", "ForkUnload", "RollerLoad", "HookLoad", "WaitDI", "SetDO", "sound", "Script" 等 - double jack_height = 0.0; ///< 顶升高度(米),用于 JackLoad/JackUnload/JackHeight - double start_height = 0.0; ///< 货叉起步前举升高度(米) - double end_height = 0.0; ///< 货叉到点后举升高度(米) - double fork_mid_height = 0.0; ///< 货叉行走过程中举升高度(米) - double fork_dist = 0.0; ///< 货叉前移距离(米),用于 ForkForward - std::string direction; ///< 方向:"left"/"right"/"front"/"back",用于辊筒或货叉 - bool recognize = false; ///< 是否启用视觉识别(用于货叉/顶升加载) - std::string recfile; ///< 识别文件路径(如 "shelf/s0002.shelf") - SoundArgs sounds_args; ///< 音频参数(当 operation="sound" 时) - WaitDIArgs wait_di_args; ///< WaitDI 参数 - SetDOArgs set_do_args; ///< SetDO 参数 - std::string script_name; ///< 脚本文件名(当 operation="Script" 时) - ScriptArgs script_args; ///< 脚本参数 - int32_t script_stage = 2; ///< 脚本执行阶段:0=导航前, 1=导航中, 2=导航后, 3=脚本控制导航 - - // -------- 原地旋转(特殊用法) -------- - double move_angle = 0.0; ///< 旋转角度(弧度),仅当 skill_name="GoByOdometer" 时使用 - double speed_w = 0.0; ///< 旋转角速度(rad/s) - int32_t loc_mode = 0; ///< 定位模式:1=激光定位, 0=里程定位 - - // -------- 自由导航(仅支持双轮差速底盘) -------- - FreeGoPoint freeGo; ///< 自由导航目标坐标(若提供此字段,则忽略 id 字段,使用坐标导航) - }; - - /** - * @brief 3051 单点导航出参 - */ - struct RobotGoTargetRes - { - int ret_code = 0; ///< 返回码,0 表示成功 - std::string create_on; ///< 数据创建时间戳 - std::string err_msg; ///< 错误信息 - }; - - //新增暂停导航响应结构体 - struct RobotTaskPauseRes { - int ret_code = 0; - std::string create_on; - std::string err_msg; - }; - - - // 继续导航响应结构体 - struct RobotTaskResumeRes { - int ret_code = 0; - std::string create_on; - std::string err_msg; - }; - - - // ==================== robot_control_stop 停止开环运动响应 ==================== - - // 0x07D0 robot_control_stop 停止开环运动返回 - struct RobotControlStopRes - { - int ret_code = 0; - std::string create_on; - std::string err_msg; - }; - - // 取消导航响应结构体 - struct RobotTaskCancelRes { - int ret_code = 0; - std::string create_on; - std::string err_msg; - }; - - // ==================== 类型别名(兼容旧代码) ==================== - using AgvState = AgvStatusInfo; - -public: - AbstractAgv() = default; - ~AbstractAgv() override = default; - - // 禁用拷贝,允许移动 - AbstractAgv(const AbstractAgv&) = delete; - AbstractAgv& operator=(const AbstractAgv&) = delete; - AbstractAgv(AbstractAgv&&) = default; - AbstractAgv& operator=(AbstractAgv&&) = default; - - // ==================== 纯虚接口 ==================== - - /** - * @brief 获取当前设备运行状态 - * @return Status 枚举值 - */ - virtual Status state() const = 0; - - /** - * @brief 获取最后一次发生的错误信息 - * @return 错误描述字符串 - */ - virtual std::string lastError() const = 0; - - /** - * @brief 启动设备(建立连接、初始化资源等) - */ - void start() override = 0; - - /** - * @brief 停止设备(断开连接、释放资源等) - */ - void stop() override = 0; - - /** - * @brief 周期性更新设备状态(由上层循环调用,可处理心跳、重连等) - */ - void update() override = 0; - - /** - * @brief 获取 AGV 状态(封装了 getStatusInfo) - * @param state 输出参数,填充 AgvState 结构 - */ - virtual void getState(AgvState& state) { - state = AgvState{}; - getStatusInfo(state); - } - - /** - * @brief 获取 AGV 基本信息(命令码 1000) - * @param info 输出参数,填充 AgvStatusInfo - */ - virtual void getStatusInfo(AgvStatusInfo& info) = 0; - - /** - * @brief 查询电池状态(命令码 1007) - * @param info 输出参数,填充 BatteryStatus - * @param simple 若为 true,只查询关键信息(可提高效率,具体取决于实现) - */ - virtual void getBatteryStatus(BatteryStatus& info, bool simple = false) = 0; - - /** - * @brief 查询机器人当前位置(命令码 1004) - * @param info 输出参数,填充 RobotLocation - */ - virtual void getRobotLocation(RobotLocation& info) = 0; - - /** - * @brief 下载指定名称的地图(命令码 4011) - * @param info 输出参数,填充 DownloadMapResult(包含地图内容) - * @param map_name 要下载的地图名称 - */ - virtual void downloadMap(DownloadMapResult& info, const std::string& map_name) = 0; - - /** - * @brief 查询地图状态(命令码 1300) - * @param info 输出参数,填充 MapStatus(当前地图、所有地图列表及文件信息) - */ - virtual void getMapStatus(MapStatus& info) = 0; - - /** - * @brief 上传地图(命令码 4010) - * @param info 输出参数,填充 UploadMapResult - * @param map_json 地图 JSON 字符串(完整的地图文件内容) - */ - virtual void uploadMap(UploadMapResult& info, const std::string& map_json) = 0; - - /** - * @brief 抢占机器人控制权(命令码 4005) - * @param info 输出参数,填充 LockResult - * @param nick_name 抢占者昵称(用于标识调用方) - */ - virtual void lockRobotControl(LockResult& info, const std::string& nick_name) = 0; - - /** - * @brief 下发开环速度运动指令(命令码 2010) - * - * 使用控制端口(19205)。下发后会强制中断当前自动导航任务。 - * 对于多舵轮设备,仅 vx/vy/w 生效。 - * - * @param res 输出参数,运动指令返回结果 - * @param req 输入参数,包含 vx/vy/w/steer/real_steer/duration - */ - virtual void robotMotionControl(MotionCtrlRes& res, const MotionCtrlReq& req) = 0; - - /** - * @brief 切换载入地图(命令码 2022) - * - * 使用控制端口(19205)。目标地图必须已在机器人中存储。 - * - * @param res 输出参数,执行结果 - * @param req 输入参数,目标地图名称 - */ - virtual void robotLoadMap(LoadMapRes& res, const LoadMapReq& req) = 0; - - /** - * @brief 查询当前导航状态(命令码 1020) - * - * 使用状态端口(19204)。 - * @param res 输出参数,导航全量状态 - * @param req 输入参数,simple=true 仅返回 task_status - */ - virtual void robotStatusTaskCurrent(RobotStatusTaskCurrentRes& res, const RobotStatusTaskCurrentReq& req) = 0; - - /** - * @brief 简单运动控制(设置速度) - * - * 默认实现抛出 logic_error,子类可重写。 - * @param vx X 方向速度 (m/s) - * @param vy Y 方向速度 (m/s) - * @param vw 角速度 (rad/s) - */ - virtual void move(double vx, double vy, double vw) { - throw std::logic_error("move not implemented"); - } - - /** - * @brief 停止运动 - * - * 默认实现抛出 logic_error,子类可重写。 - */ - virtual void stopMove() { - throw std::logic_error("stopMove not implemented"); - } - - /** - * @brief 查询当前机器人控制权持有者(命令码 1060) - * @param info 输出参数,填充 CurrentLockStatus - */ - virtual void getCurrentLockStatus(CurrentLockStatus& info) = 0; - - /** - * @brief 查询地图载入状态(命令码 1022) - * - * 使用状态端口(19204)。 - * @param res 输出参数,包含 loadmap_status(0=失败, 1=成功, 2=载入中) - * @note 载入中(2)时禁止执行重定位操作 - */ - virtual void queryLoadMapStatus(QueryLoadMapStatusRes& res) = 0; - - /** - * @brief 查询当前地图全部站点信息(命令码 1301) - * - * 使用状态端口(19204)。 - * @param res 输出参数,包含站点数组与执行结果 - */ - virtual void queryStationList(QueryStationRes& res) = 0; - - /** - * @brief 指定路径导航(命令码 3066) - * - * 使用导航端口(19206)。任务串行追加执行。 - * - * @param res 输出参数,下发任务结果(ret_code=0 仅表示接收成功,不代表执行完成) - * @param req 输入参数,多段站点导航序列(每个任务必须包含 task_id, source_id, id) - * @attention - * - source_id 和 id 之间必须有直接相连的线路,不可以跳点。 - * - 任务会排队执行,若前一个任务失败,后续任务自动取消。 - * - 适合多车调度场景,因为路径可预知、不会自主绕路。 - */ - virtual void robotGoTargetList(GoTargetListRes& res, const GoTargetListReq& req) = 0; - - /** - * @brief 批量查询任务状态(命令码 1110) - * - * 使用状态端口(19204)。 - * @param res 输出参数,包含所有任务状态包(已完成+未完成) - * @param req 输入参数,要查询的 task_id 列表;若为空,则查询所有未完成+最近一条完成的任务 - */ - virtual void robotStatusTaskPackage(QueryTaskStatusPackageRes& res, const QueryTaskStatusPackageReq& req) = 0; - - /** - * @brief 单点站点自动规划导航(命令码 3051) - * - * 使用导航端口(19206)。 - * @param res 输出参数,下发结果(ret_code=0 仅表示接收成功) - * @param req 完整导航参数(支持自由导航、固定路径导航、带设备操作等) - * @warning - * - **严禁用于多车调度场景**,仅限单车测试/任务链验证。 - * - 下发新任务会取消当前正在执行的任务(不排队)。 - * - 成功下发后会**自动清除**指定的 warning/error 报错码(详见协议文档)。 - * - 支持自由导航(freeGo),但仅限双轮差速底盘。 - */ - virtual void robotGoTarget(RobotGoTargetRes& res, const RobotGoTargetReq& req) = 0; - - - /** - * @brief 2000(0x07D0) robot_control_stop_req 停止开环速度运动指令 - * @param res 出参,执行结果 - */ - virtual void robotControlStop(RobotControlStopRes& res) = 0; - - - /** - * @brief 暂停当前导航任务(命令码 3001, 0x0BB9) - * @param res 输出参数,执行结果 - */ - virtual void robotTaskPause(RobotTaskPauseRes& res) = 0; - - - /** - * @brief 继续当前导航任务(命令码 3002, 0x0BBA) - * @param res 输出参数,执行结果 - */ - virtual void robotTaskResume(RobotTaskResumeRes& res) = 0; - - - /** - * @brief 取消当前导航任务(命令码 3003, 0x0BBB) - * @param res 输出参数,执行结果 - */ - virtual void robotTaskCancel(RobotTaskCancelRes& res) = 0; - - - - -protected: - // ==================== 保护成员变量 ==================== - std::string id_; ///< 设备 ID(唯一标识) - std::string ip_; ///< 设备 IP 地址 - - // 多端口配置(对应 proto 定义) - int port_status_{0}; ///< 状态查询端口(默认 19204) - int port_control_{0}; ///< 控制 API 端口(默认 19205) - int port_nav_{0}; ///< 导航 API 端口(默认 19206) - int port_config_{0}; ///< 配置 API 端口(默认 19207) - int port_other_{0}; ///< 其他 API 端口(默认 19210) - int port_push_{0}; ///< 推送端口(默认 19301) - - bool enable_{false}; ///< 设备启用标志 -}; - -} // namespace cmvr::device - -#endif // CMVR_ES_ABSTRACT_AGV_H \ No newline at end of file +#endif //CMVR_ES_ABSTRACT_AGV_H \ No newline at end of file diff --git a/cmvr-es/devices/agv/agv_src1100/CMakeLists.txt b/cmvr-es/devices/agv/agv_src1100/CMakeLists.txt deleted file mode 100644 index f18e7907..00000000 --- a/cmvr-es/devices/agv/agv_src1100/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -add_library(agv_src1100 SHARED src/agv_src1100.cpp) - -target_include_directories(agv_src1100 PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/include - ${JSONCPP_INCLUDE_DIRS} -) - -add_library(cmvr_es::device::agv_src1100 ALIAS agv_src1100) - -# 关键修改:PRIVATE -> PUBLIC -target_link_libraries(agv_src1100 PUBLIC - jsoncpp - cmvr_es::common - cmvr_es::proto - glog - pthread -) - -install(TARGETS agv_src1100 LIBRARY DESTINATION lib) \ No newline at end of file diff --git a/cmvr-es/devices/agv/agv_src1100/include/agv_src1100.h b/cmvr-es/devices/agv/agv_src1100/include/agv_src1100.h deleted file mode 100644 index 13ab8d32..00000000 --- a/cmvr-es/devices/agv/agv_src1100/include/agv_src1100.h +++ /dev/null @@ -1,307 +0,0 @@ -// -// Created by linbo on 2025/6/20. -// -/** - * @file agv_src1100.h - * @brief 仙工智能 SRC-1100/2200 系列 AGV 控制器设备实现类的头文件。 - * 继承自 AbstractAgv,实现了所有纯虚接口,并管理多端口 TCP 连接。 - */ -#ifndef AGV_SRC1100_H -#define AGV_SRC1100_H -#pragma once - -#include "./agv/abstract_agv.h" -#include -#include -#include -#include "nlohmann/json.hpp" - -using json = nlohmann::json; - -namespace cmvr::device { - -/** - * @brief 仙工智能 SRC-1100/2200 系列 AGV 控制器设备实现类 - * - * 继承自 AbstractAgv,实现了与仙工智能 SRC 系列控制器通信的具体协议。 - * 支持多端口(状态、控制、导航、配置、推送)并发连接和指令交互。 - * - * @note 该类不是线程安全的,外部调用需自行保证同一设备实例的串行访问。 - */ -class AgvSrc1100 final : public AbstractAgv { -public: - // ==================== 构造 / 析构 ==================== - - /** - * @brief 从 XML 配置节点构造设备实例(已弃用或未使用,保留兼容) - * @param cfg XML 配置节点,包含设备 ID、IP、端口、启用标志等 - */ - explicit AgvSrc1100(const XmlNode& cfg); - - /** - * @brief 从 Protobuf 配置对象构造设备实例 - * @param cfg AGVsrc1100Config 配置对象,包含设备 ID、IP、端口、启用标志等 - */ - explicit AgvSrc1100(const config::AGVsrc1100Config& cfg); - - /** - * @brief 析构函数,自动停止设备并释放资源 - */ - ~AgvSrc1100() override; - - // ==================== 生命周期管理(通用) ==================== - - /** - * @brief 获取当前设备运行状态 - * @return Status 枚举值(CREATED / INITIALIZED / RUNNING / PAUSED / STOPPED / FAULT) - */ - Status state() const override; - - /** - * @brief 获取最后一次发生的错误信息 - * @return 错误描述字符串,若无错误则返回空字符串 - */ - std::string lastError() const override; - - /** - * @brief 启动设备,标记为运行状态 - * @note 所有 socket 连接应在构造时已完成,启动仅改变状态标志 - */ - void start() override; - - /** - * @brief 停止设备,关闭所有 TCP 连接并释放资源 - */ - void stop() override; - - /** - * @brief 周期性更新设备状态 - * @note 当前为空实现,可扩展心跳检测或重连逻辑 - */ - void update() override; - - // ==================== 端口 19204 – 机器人状态 API(允许 10 个连接) ==================== - // 功能:查询机器人各种状态信息(只读操作,不改变机器人状态) - - /** - * @brief 查询机器人基本信息(命令码 1000, robot_status_info_req) - * @param info 输出参数,填充 AgvStatusInfo - * @note 返回信息包括:版本、型号、地图名称、网络 IP、MAC、Wi-Fi 信号等 - */ - void getStatusInfo(AgvStatusInfo& info) override; - - /** - * @brief 查询电池状态(命令码 1007, robot_status_battery_req) - * @param info 输出参数,填充 BatteryStatus - * @param simple 若为 true,仅返回关键电量信息(当前实现未区分) - * @note 返回信息包括:电量百分比、温度、充放电状态、电压、电流等 - */ - void getBatteryStatus(BatteryStatus& info, bool simple = false) override; - - /** - * @brief 查询机器人当前位置(命令码 1004, robot_status_loc_req) - * @param info 输出参数,填充 RobotLocation - * @note 返回信息包括:世界坐标系 X/Y 坐标、朝向角、定位置信度、当前站点 - */ - void getRobotLocation(RobotLocation& info) override; - - /** - * @brief 查询地图状态(命令码 1300, robot_status_map_req) - * @param info 输出参数,填充 MapStatus - * @note 返回信息包括:当前载入的地图名称、所有存储的地图列表、文件详情 - */ - void getMapStatus(MapStatus& info) override; - - /** - * @brief 查询当前控制权持有者(命令码 1060, robot_status_current_lock_req) - * @param info 输出参数,填充 CurrentLockStatus - * @note 返回信息包括:是否被锁定、持有者 IP/端口/昵称、锁定时间等 - */ - void getCurrentLockStatus(CurrentLockStatus& info) override; - - /** - * @brief 查询地图载入状态(命令码 1022, robot_status_loadmap_req) - * @param res 输出参数,包含 loadmap_status - * @note loadmap_status: 0=失败, 1=成功, 2=载入中(载入中禁止重定位) - */ - void queryLoadMapStatus(QueryLoadMapStatusRes& res) override; - - /** - * @brief 查询当前地图站点列表(命令码 1301, robot_status_station_req) - * @param res 输出参数,填充 QueryStationRes,包含所有站点信息 - * @note 返回信息包括:站点 ID、类型、坐标、朝向角、属性等 - */ - void queryStationList(QueryStationRes& res) override; - - /** - * @brief 查询当前导航状态(命令码 1020, robot_status_task_req) - * @param res 输出参数,填充 RobotStatusTaskCurrentRes - * @param req 输入参数,simple=true 时只返回 task_status - * @note 返回信息包括:任务状态、任务类型、目标站点/坐标、已走/未走路径 - */ - void robotStatusTaskCurrent(RobotStatusTaskCurrentRes& res, const RobotStatusTaskCurrentReq& req) override; - - /** - * @brief 批量查询任务状态(命令码 1110, robot_status_task_status_package_req) - * @param res 输出参数,填充 QueryTaskStatusPackageRes - * @param req 输入参数,task_ids 列表;若为空则查询所有未完成 + 最近一条已完成的任务 - * @note 返回信息包括:任务列表、每个任务的 ID/状态/类型、进度百分比、剩余距离等 - */ - void robotStatusTaskPackage(QueryTaskStatusPackageRes& res, const QueryTaskStatusPackageReq& req) override; - - // ==================== 端口 19205 – 机器人控制 API(允许 5 个连接) ==================== - // 功能:下发控制指令,改变机器人运动或状态(非导航类指令) - - /** - * @brief 下发开环速度运动指令(命令码 2010, robot_control_motion_req) - * @param res 输出参数,执行结果 - * @param req 输入参数,包含 vx/vy/w/steer/real_steer/duration - * @warning 下发此指令会立即取消当前正在执行的导航任务 - * @note 多舵轮设备仅 vx/vy/w 生效,steer/real_steer 仅单舵轮设备有效 - */ - void robotMotionControl(MotionCtrlRes& res, const MotionCtrlReq& req) override; - - /** - * @brief 切换载入地图(命令码 2022, robot_control_loadmap_req) - * @param res 输出参数,执行结果 - * @param req 输入参数,目标地图名称 - * @note 目标地图必须已存在于机器人中,否则切换失败 - */ - void robotLoadMap(LoadMapRes& res, const LoadMapReq& req) override; - - // ==================== 端口 19206 – 机器人导航 API(允许 5 个连接) ==================== - // 功能:下发导航任务 - - /** - * @brief 指定路径导航(命令码 3066, robot_task_gotargetlist_req) - * @param res 输出参数,下发结果(ret_code=0 仅表示指令被接收,不表示执行完成) - * @param req 输入参数,包含 move_task_list(站点序列) - * @attention - * - 每个任务必须含 task_id、source_id、id 三个必填字段 - * - source_id 和 id 之间必须有直接相连的线路,不可跳点 - * - 任务会排队执行,前一个任务失败时后续任务自动取消 - * - 适合多车调度场景 - */ - void robotGoTargetList(GoTargetListRes& res, const GoTargetListReq& req) override; - - /** - * @brief 单点站点自动规划导航(命令码 3051, robot_task_gotarget_req) - * @param res 输出参数,下发结果(ret_code=0 仅表示指令被接收) - * @param req 输入参数,支持自由导航(freeGo)和基于站点的路径导航两种模式 - * @warning - * - **严禁用于多车调度场景**,仅限单车测试/任务链验证 - * - 下发新任务会取消当前正在执行的任务(不排队) - * - 成功下发后会自动清除指定的 warning/error 报错码 - * - 自由导航(freeGo)仅支持双轮差速底盘 - */ - void robotGoTarget(RobotGoTargetRes& res, const RobotGoTargetReq& req) override; - - // ==================== 端口 19207 – 机器人配置 API(允许 5 个连接) ==================== - // 功能:配置类操作(地图上传/下载、控制权管理、参数修改等) - - /** - * @brief 抢占机器人控制权(命令码 4005, robot_config_lock_req) - * @param info 输出参数,填充 LockResult - * @param nick_name 抢占者昵称,用于标识调用方 - * @note 抢占成功后调用方获得独占控制权,其他方仅可查询状态 - */ - void lockRobotControl(LockResult& info, const std::string& nick_name) override; - - /** - * @brief 上传地图(命令码 4010, robot_config_uploadmap_req) - * @param info 输出参数,填充 UploadMapResult - * @param map_json 地图 JSON 字符串(完整的地图文件内容) - * @note 地图数据较大时会自动分片发送,需确保 JSON 格式正确 - */ - void uploadMap(UploadMapResult& info, const std::string& map_json) override; - - /** - * @brief 下载指定地图(命令码 4011, robot_config_downloadmap_req) - * @param info 输出参数,填充 DownloadMapResult(包含地图 JSON 内容) - * @param map_name 要下载的地图名称 - * @note 下载的地图内容以 JSON 字符串形式存储在 info.map_content 中 - */ - void downloadMap(DownloadMapResult& info, const std::string& map_name) override; - - // ==================== 端口 19210 – 其他 API(允许 5 个连接) ==================== - // 功能:外设控制(顶升/货叉/辊筒/音频/IO 等) - // 当前未实现具体方法,预留扩展 - - // ==================== 端口 19301 – 机器人推送 API(允许 10 个连接) ==================== - // 功能:接收机器人主动推送的实时状态数据 - // 相关配置方法(如 9300)可能在此,当前未实现 - - - // ---------- 新增任务控制接口(3001/3002/3003)及停止运动(2000) ---------- - void robotTaskPause(RobotTaskPauseRes& res) override; - void robotTaskResume(RobotTaskResumeRes& res) override; - void robotTaskCancel(RobotTaskCancelRes& res) override; - void robotControlStop(RobotControlStopRes& res) override; - - -private: - // ==================== 私有通信辅助函数 ==================== - - /** - * @brief 异步连接指定端口 - * @param sock 输出参数,连接成功后存储 socket 文件描述符 - * @param port 目标端口号(19204/19205/19206/19207/19210/19301) - * @note 在独立线程中执行,连接成功后将 sock 设置为有效值 - */ - void asyncConnect(int& sock, int port); - - /** - * @brief 发送请求并接收完整响应 - * @param sock 已连接的 socket 文件描述符 - * @param header 16 字节协议帧头 - * @return true 表示收发成功,current_json_ 中存储响应 JSON;false 表示失败 - * @note 内部先发送帧头,然后循环接收直到超时,最后提取 JSON 部分 - */ - bool sendAndRecv(int sock, const uint8_t* header); - - /** - * @brief 清空 socket 接收缓冲区中的残留数据 - * @param sock 目标 socket 文件描述符 - * @note 采用非阻塞方式读取并丢弃所有可读数据,防止粘包干扰 - */ - void flushSocket(int sock); - - /** - * @brief 设置 socket 接收超时时间 - * @param sock 目标 socket 文件描述符 - * @param timeout_ms 超时时间(毫秒),0 表示取消超时 - */ - void setSocketTimeout(int sock, int timeout_ms); - - /** - * @brief 解析 JSON 字符串并做异常捕获 - * @param json_str 输入的 JSON 字符串 - * @param root 输出参数,解析后的 json 对象 - * @param err_msg 输出参数,解析失败时的错误信息 - * @return true 表示解析成功,false 表示解析失败 - */ - bool parseJson(const std::string& json_str, json& root, std::string& err_msg); - - - // ==================== 成员变量 ==================== - - mutable std::mutex mutex_; ///< 保护状态和错误信息的互斥锁 - std::atomic status_{Status::CREATED}; ///< 当前设备运行状态(原子变量) - std::string last_error_; ///< 最后一次错误信息 - std::string current_json_; ///< 最近一次响应的 JSON 内容(由 sendAndRecv 填充) - - // ---------- 各端口 Socket 文件描述符 ---------- - int sock_status_ = -1; ///< 19204 – 状态 API 端口 - int sock_control_ = -1; ///< 19205 – 控制 API 端口 - int sock_nav_ = -1; ///< 19206 – 导航 API 端口 - int sock_config_ = -1; ///< 19207 – 配置 API 端口 - int sock_other_ = -1; ///< 19210 – 其他 API 端口 - int sock_push_ = -1; ///< 19301 – 推送 API 端口 - - std::vector connect_threads_; ///< 各端口异步连接线程 - mutable std::mutex connect_mutex_; ///< 保护 connect_threads_ 的互斥锁 -}; - -} // namespace cmvr::device - -#endif // AGV_SRC1100_H \ No newline at end of file diff --git a/cmvr-es/devices/agv/agv_src1100/src/agv_src1100.cpp b/cmvr-es/devices/agv/agv_src1100/src/agv_src1100.cpp deleted file mode 100644 index 558e621b..00000000 --- a/cmvr-es/devices/agv/agv_src1100/src/agv_src1100.cpp +++ /dev/null @@ -1,2771 +0,0 @@ -// ============================================================================ -// 文件名: agv_src1100.cpp -// 描述: 仙工智能 SRC-1100/2200 系列 AGV 控制器设备实现 -// 端口分类: -// 19204 - 机器人状态 API(查询位置/电池/任务/地图/控制权等) -// 19205 - 机器人控制 API(开环运动/切换地图等) -// 19206 - 机器人导航 API(路径导航/指定路径导航等) -// 19207 - 机器人配置 API(地图上传下载/控制权抢占等) -// 19210 - 其他 API(外设控制/音频/IO 等)【未实现】 -// 19301 - 机器人推送 API(主动状态推送)【未实现】 -// ============================================================================ - -#include "../include/agv_src1100.h" -#include -#include -#include -#include -#include -#include -#include -#include -// #include "json/json.h" -#include "nlohmann/json.hpp" -using json = nlohmann::json; -namespace cmvr::device -{ - // ============================================================================ - // 构造函数 / 析构函数 - // ============================================================================ - - /** - * @brief 从 Protobuf 配置对象构造设备实例 - * @param cfg AGVsrc1100Config 配置对象 - * @note 连接所有端口:19204/19205/19206/19207/19210/19301 - */ - AgvSrc1100::AgvSrc1100(const config::AGVsrc1100Config& cfg) : AbstractAgv() { - // 打印构造日志,便于调试追踪 - LOG(INFO) << "[AgvSrc1100 Create] this_addr=" << this - << " cfg_id=" << cfg.id() - << " cfg_ip=" << cfg.ip() - << " cfg_port_config=" << cfg.port_config(); - - // 从配置中读取设备标识信息 - id_ = cfg.id(); // 设备 ID - ip_ = cfg.ip(); // 设备 IP 地址 - enable_ = cfg.enable(); // 是否启用 - - // 初始化各端口号(对应仙工协议的不同服务端口) - port_status_ = cfg.port_status(); // 19204 – 状态查询 - port_control_ = cfg.port_control(); // 19205 – 控制指令 - port_nav_ = cfg.port_nav(); // 19206 – 导航任务 - port_config_ = cfg.port_config(); // 19207 – 配置操作 - port_other_ = cfg.port_other(); // 19210 – 其他外设 - port_push_ = cfg.port_push(); // 19301 – 数据推送 - - // 如果设备启用,则异步连接所有端口 - if (enable_) { - connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_status_), port_status_); - connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_control_), port_control_); - connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_nav_), port_nav_); - connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_config_), port_config_); - connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_other_), port_other_); - connect_threads_.emplace_back(&AgvSrc1100::asyncConnect, this, std::ref(sock_push_), port_push_); - } - - LOG(INFO) << "port_status=" << port_status_ - << ", port_control=" << port_control_ - << ", port_nav=" << port_nav_ - << ", port_config=" << port_config_ - << ", port_other=" << port_other_ - << ", port_push=" << port_push_; - } - - /** - * @brief 析构函数:停止设备并等待所有连接线程结束 - */ - AgvSrc1100::~AgvSrc1100() { - stop(); // 关闭所有 socket - // 等待所有异步连接线程结束 - for (auto& t : connect_threads_) { - if (t.joinable()) t.join(); - } - } - - // ============================================================================ - // 生命周期管理(通用接口) - // ============================================================================ - - /** - * @brief 获取当前设备运行状态 - * @return Status 枚举值 - */ - AbstractAgv::Status AgvSrc1100::state() const { - return status_; - } - - /** - * @brief 获取最后一次发生的错误信息 - * @return 错误描述字符串 - */ - std::string AgvSrc1100::lastError() const { - std::lock_guard lock(mutex_); - return last_error_; - } - - /** - * @brief 启动设备:标记为运行状态 - * @note socket 连接已在构造时完成,此处仅改变状态标志 - */ - void AgvSrc1100::start() { - status_ = Status::RUNNING; - } - - /** - * @brief 停止设备:关闭所有 TCP 连接并释放资源 - */ - void AgvSrc1100::stop() { - // 定义 lambda 关闭单个 socket 并置为 -1 - auto closeSocket = [](int& s) { - if (s >= 0) { close(s); s = -1; } - }; - // 关闭所有端口的 socket - closeSocket(sock_status_); - closeSocket(sock_control_); - closeSocket(sock_nav_); - closeSocket(sock_config_); - closeSocket(sock_other_); - closeSocket(sock_push_); - status_ = Status::STOPPED; - } - - /** - * @brief 周期性更新设备状态 - * @note 当前为空实现,预留扩展(如心跳检测、自动重连等) - */ - void AgvSrc1100::update() {} - - // ============================================================================ - // 私有通信辅助函数 - // ============================================================================ - - /** - * @brief 清空 socket 接收缓冲区残留数据,防止粘包 - * @param sock 目标 socket - * @note 非阻塞读取并丢弃所有可读数据 - */ - void AgvSrc1100::flushSocket(int sock) { - uint8_t tmp[128]; // 临时缓冲区 - ssize_t rlen; - // MSG_DONTWAIT: 非阻塞模式,读到没有数据时立即返回 - while ((rlen = recv(sock, tmp, sizeof(tmp), MSG_DONTWAIT)) > 0); - } - - /** - * @brief 设置 socket 接收超时时间 - * @param sock 目标 socket - * @param timeout_ms 超时时间(毫秒),0 表示取消超时 - */ - void AgvSrc1100::setSocketTimeout(int sock, int timeout_ms) { - struct timeval tv; - tv.tv_sec = timeout_ms / 1000; // 秒 - tv.tv_usec = (timeout_ms % 1000) * 1000; // 微秒 - // SO_RCVTIMEO: 设置接收超时 - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); - } - - /** - * @brief 异步连接指定端口 - * @param sock 输出参数,连接成功后的 socket 描述符 - * @param port 目标端口号 - * @note 在独立线程中执行,连接失败时设置错误状态 - */ - void AgvSrc1100::asyncConnect(int& sock, int port) { - // 创建 TCP socket - int s = socket(AF_INET, SOCK_STREAM, 0); - if (s < 0) { - std::lock_guard lock(mutex_); - last_error_ = "socket create failed"; - status_ = Status::FAULT; - return; - } - - // 配置目标地址 - struct sockaddr_in addr{}; - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - inet_pton(AF_INET, ip_.c_str(), &addr.sin_addr); // IP 地址转换 - - // 发起 TCP 连接 - if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) { - close(s); // 连接失败,关闭 socket - std::lock_guard lock(mutex_); - last_error_ = "connect failed port:" + std::to_string(port); - status_ = Status::FAULT; - return; - } - - // 连接成功,保存 socket 描述符 - { - std::lock_guard lock(mutex_); - sock = s; - status_ = Status::INITIALIZED; - } - } - - /** - * @brief 发送请求并接收完整响应 - * @param sock 已连接的 socket - * @param header 16 字节协议帧头(包含命令码等) - * @return true 表示成功,current_json_ 存储响应 JSON - * @note 适用于无负载或简单查询命令(如 1000/1004/1007) - */ - bool AgvSrc1100::sendAndRecv(int sock, const uint8_t* header) { - // 加锁,防止多个线程同时操作 current_json_ - std::lock_guard lock(mutex_); - - if (sock < 0) { - last_error_ = "socket not connected"; - return false; - } - - // 清空残留数据,防止读取到上一次的响应 - flushSocket(sock); - - // 发送 16 字节协议帧头 - if (send(sock, header, 16, MSG_NOSIGNAL) != 16) { - last_error_ = "send header failed"; - return false; - } - - // 设置接收超时 100ms,避免无限阻塞 - setSocketTimeout(sock, 100); - - const int RECV_BUF_LEN = 16384; - uint8_t recvBuf[RECV_BUF_LEN] = {0}; - int totalRecv = 0; - - // 循环接收数据,直到读空或缓冲区满 - while (totalRecv < RECV_BUF_LEN) - { - int ret = recv(sock, recvBuf + totalRecv, RECV_BUF_LEN - totalRecv, 0); - if (ret <= 0) break; // 读空或超时,退出循环 - totalRecv += ret; - } - - // 取消超时设置 - setSocketTimeout(sock, 0); - - if (totalRecv <= 0) { - last_error_ = "no data received"; - return false; - } - - // 将接收到的数据转为字符串 - std::string rawData(reinterpret_cast(recvBuf), totalRecv); - // 查找 JSON 起始和结束位置 - size_t jsonStart = rawData.find('{'); - size_t jsonEnd = rawData.rfind('}'); - - // 提取 JSON 内容,兜底处理不完整数据 - if (jsonStart == std::string::npos) - { - last_error_ = "can not find '{'"; - return false; - } - if (jsonEnd == std::string::npos) - { - // 找不到 },截取到末尾并补上 } - current_json_ = rawData.substr(jsonStart) + "}"; - } - else - { - current_json_ = rawData.substr(jsonStart, jsonEnd - jsonStart + 1); - } - - fflush(stdout); - last_error_ = "ok"; - return true; - } - - /** - * @brief 解析 JSON 字符串并做异常捕获 - * @param json_str 输入的 JSON 字符串 - * @param root 输出参数,解析后的 json 对象 - * @param err_msg 输出参数,解析失败时的错误信息 - * @return true 表示解析成功,false 表示失败 - */ - bool AgvSrc1100::parseJson(const std::string& json_str, json& root, std::string& err_msg) - { - root.clear(); - try - { - root = json::parse(json_str); // 使用 nlohmann/json 解析 - } - catch (json::parse_error& e) // 捕获解析异常 - { - err_msg = "JSON parse error: " + std::string(e.what()); - return false; - } - return true; - } - - // ============================================================================ - // 端口 19204 – 机器人状态 API(允许 10 个连接) - // 功能:查询机器人各种状态信息(只读操作) - // ============================================================================ - - // -------------------------------------------------------------------------- - // 1000 - robot_status_info_req 查询机器人基本信息 - // -------------------------------------------------------------------------- - void AgvSrc1100::getStatusInfo(AgvStatusInfo& info) { - int sock = sock_status_; - if (sock < 0) { - info.ret_code = -1; - info.err_msg = "socket not connected"; - return; - } - - // 构造 16 字节协议帧头 - // 格式: 0x5A 0x01 0x00 0x01 + 长度(4字节) + 命令码(2字节) + 保留(6字节) - // 命令码 0x03E8 = 1000 - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, - 0x03, 0xE8, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - }; - - // 发送请求并接收响应 - if (!sendAndRecv(sock, send_buf)) { - info.ret_code = -2; - info.err_msg = last_error_; - return; - } - - // 从成员变量中读取响应 JSON - std::string json_str; - { - std::lock_guard lk(mutex_); - json_str = current_json_; - } - - // 兜底修复 JSON 格式(补全花括号) - if (!json_str.empty()) - { - if (json_str.front() != '{') json_str = "{" + json_str; - if (json_str.back() != '}') json_str += "}"; - } - - if (json_str.empty()) { - info.ret_code = -4; - info.err_msg = "empty json data"; - return; - } - - // 解析 JSON - json root; - std::string err; - if (!parseJson(json_str, root, err)) { - info.ret_code = -3; - info.err_msg = err; - return; - } - - // 提取字段,不存在的字段使用默认值 - info.id = root.value("id", ""); - info.vehicle_id = root.value("vehicle_id", ""); - info.version = root.value("version", ""); - info.model = root.value("model", ""); - info.dsp_version = root.value("dsp_version", ""); - info.current_ip = root.value("current_ip", ""); - info.mac = root.value("MAC", ""); - info.rssi = root.value("rssi", 0); - info.ret_code = root.value("ret_code", 0); - info.err_msg = root.value("err_msg", ""); - } - - - // -------------------------------------------------------------------------- - // 1007 - robot_status_battery_req 查询电池状态 - // -------------------------------------------------------------------------- - void AgvSrc1100::getBatteryStatus(BatteryStatus& info, bool simple) - { - int sock = sock_status_; - if (sock < 0) - { - info.ret_code = -1; - info.err_msg = "socket not connected"; - return; - } - - // 命令码 0x03EF = 1007 - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, - 0x03, 0xEF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - }; - - if (!sendAndRecv(sock, send_buf)) - { - info.ret_code = -2; - info.err_msg = last_error_; - return; - } - - std::string json_str; - { - std::lock_guard lk(mutex_); - json_str = current_json_; - } - - if (!json_str.empty()) - { - if (json_str.front() != '{') json_str = "{" + json_str; - if (json_str.back() != '}') json_str += "}"; - } - - if (json_str.empty()) - { - info.ret_code = -4; - info.err_msg = "empty json data"; - return; - } - - json root; - std::string err; - if (!parseJson(json_str, root, err)) - { - info.ret_code = -3; - info.err_msg = err; - return; - } - - // 提取电池相关字段 - info.battery_level = root.value("battery_level", 0.0); - info.battery_temp = root.value("battery_temp", 0.0); - info.charging = root.value("charging", false); - info.voltage = root.value("voltage", 0.0); - info.current = root.value("current", 0.0); - info.max_charge_voltage = root.value("max_charge_voltage", -1.0); - info.max_charge_current = root.value("max_charge_current", -1.0); - info.manual_charge = root.value("manual_charge", false); - info.auto_charge = root.value("auto_charge", false); - info.battery_cycle = root.value("battery_cycle", 0); - info.battery_user_data = root.value("battery_user_data", ""); - info.extra = root.value("extra", ""); - info.ret_code = root.value("ret_code", 0); - info.create_on = root.value("create_on", ""); - info.err_msg = root.value("err_msg", ""); - } - - - // -------------------------------------------------------------------------- - // 1004 - robot_status_loc_req 查询机器人位置 - // -------------------------------------------------------------------------- - void AgvSrc1100::getRobotLocation(RobotLocation& info) - { - int sock = sock_status_; - if (sock < 0) - { - info.ret_code = -1; - info.err_msg = "socket not connected"; - return; - } - - // 命令码 0x03EC = 1004 - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, - 0x03, 0xEC, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - }; - - if (!sendAndRecv(sock, send_buf)) - { - info.ret_code = -2; - info.err_msg = last_error_; - return; - } - - std::string json_str; - { - std::lock_guard lk(mutex_); - json_str = current_json_; - } - - if (!json_str.empty()) - { - if (json_str.front() != '{') json_str = "{" + json_str; - if (json_str.back() != '}') json_str += "}"; - } - - if (json_str.empty()) - { - info.ret_code = -4; - info.err_msg = "empty json data"; - return; - } - - json root; - std::string err; - if (!parseJson(json_str, root, err)) - { - info.ret_code = -3; - info.err_msg = err; - return; - } - - // 提取位置相关字段 - info.x = root.value("x", 0.0); - info.y = root.value("y", 0.0); - info.angle = root.value("angle", 0.0); - info.confidence = root.value("confidence", 0.0); - info.current_station = root.value("current_station", ""); - info.last_station = root.value("last_station", ""); - info.loc_method = root.value("loc_method", 0); - info.ret_code = root.value("ret_code", 0); - info.create_on = root.value("create_on", ""); - info.err_msg = root.value("err_msg", ""); - } - - - // -------------------------------------------------------------------------- - // 1300 - robot_status_map_req 查询地图状态 - // -------------------------------------------------------------------------- - void AgvSrc1100::getMapStatus(MapStatus& info) - { - int sock = sock_status_; - if (sock < 0) - { - info.ret_code = -1; - info.err_msg = "socket not connected"; - return; - } - - // 命令码 0x0514 = 1300 - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, - 0x05, 0x14, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - }; - - if (!sendAndRecv(sock, send_buf)) - { - info.ret_code = -2; - info.err_msg = last_error_; - return; - } - - std::string json_str; - { - std::lock_guard lk(mutex_); - json_str = current_json_; - } - - if (!json_str.empty()) - { - if (json_str.front() != '{') json_str = "{" + json_str; - if (json_str.back() != '}') json_str += "}"; - } - - if (json_str.empty()) - { - info.ret_code = -4; - info.err_msg = "empty json data"; - return; - } - - json root; - std::string err; - if (!parseJson(json_str, root, err)) - { - info.ret_code = -3; - info.err_msg = err; - return; - } - - // 提取地图字段 - info.current_map = root.value("current_map", ""); - info.current_map_md5 = root.value("current_map_md5", ""); - info.ret_code = root.value("ret_code", 0); - info.create_on = root.value("create_on", ""); - info.err_msg = root.value("err_msg", ""); - - // 解析地图名称数组 - if (root.contains("maps") && root["maps"].is_array()) - { - for (auto& item : root["maps"]) - { - info.maps.push_back(item.get()); - } - } - - // 解析地图文件详情数组 - if (root.contains("map_files_info") && root["map_files_info"].is_array()) - { - for (auto& item : root["map_files_info"]) - { - MapFileInfo fileInfo; - fileInfo.name = item.value("name", ""); - fileInfo.modified = item.value("modified", ""); - fileInfo.size = item.value("size", 0LL); - info.map_files_info.push_back(fileInfo); - } - } - } - - - // -------------------------------------------------------------------------- - // 1022 - robot_status_loadmap_req 查询地图载入状态 - // -------------------------------------------------------------------------- - void AgvSrc1100::queryLoadMapStatus(QueryLoadMapStatusRes& res) - { - int sock = sock_status_; - if (sock < 0) - { - res.ret_code = -1; - res.err_msg = "socket not connected"; - return; - } - - // 命令码 0x03FE = 1022,无负载,长度=0 - uint16_t payload_len = 0; - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, - static_cast((payload_len >> 8) & 0xFF), // 长度高字节 - static_cast(payload_len & 0xFF), // 长度低字节 - 0x03, 0xFE, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - setSocketTimeout(sock, 1000); // 1秒超时 - - // 发送帧头(有重试机制) - int total_sent = 0; - int retry_cnt = 3; - while (total_sent < 16 && retry_cnt > 0) - { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); - if (ret <= 0) - { - retry_cnt--; - usleep(10000); - continue; - } - total_sent += ret; - } - if (total_sent != 16) - { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send frame header failed"; - return; - } - - // 接收响应 - const int recv_buf_len = 65536; - std::vector recv_buf(recv_buf_len); - std::string raw_data; - int recv_ret = 0; - while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) - { - raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); - } - setSocketTimeout(sock, 0); - - if (raw_data.empty()) - { - res.ret_code = -2; - res.err_msg = "no response data"; - return; - } - - // 提取 JSON - size_t json_start = raw_data.find('{'); - size_t json_end = raw_data.rfind('}'); - std::string json_str; - if (json_start == std::string::npos) - { - res.ret_code = -4; - res.err_msg = "json not found in response"; - return; - } - if (json_end == std::string::npos) - { - json_str = raw_data.substr(json_start) + "}"; - } - else - { - json_str = raw_data.substr(json_start, json_end - json_start + 1); - } - - // 解析 JSON - json root; - std::string parse_err; - if (!parseJson(json_str, root, parse_err)) - { - res.ret_code = -3; - res.err_msg = "json parse error: " + parse_err; - return; - } - - // loadmap_status: 0=失败, 1=成功, 2=载入中 - res.loadmap_status = root.value("loadmap_status", 0); - res.ret_code = root.value("ret_code", 0); - res.create_on = root.value("create_on", ""); - res.err_msg = root.value("err_msg", ""); - } - - - // -------------------------------------------------------------------------- - // 1301 - robot_status_station_req 查询站点列表 - // -------------------------------------------------------------------------- - void AgvSrc1100::queryStationList(QueryStationRes& res) - { - int sock = sock_status_; - if (sock < 0) - { - res.ret_code = -1; - res.err_msg = "socket not connected"; - return; - } - - // 命令码 0x0515 = 1301,无负载,长度=0 - uint16_t payload_len = 0; - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, - static_cast((payload_len >> 8) & 0xFF), - static_cast(payload_len & 0xFF), - 0x05, 0x15, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - setSocketTimeout(sock, 1000); - - int total_sent = 0; - int retry_cnt = 3; - while (total_sent < 16 && retry_cnt > 0) - { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); - if (ret <= 0) - { - retry_cnt--; - usleep(10000); - continue; - } - total_sent += ret; - } - if (total_sent != 16) - { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send frame header failed"; - return; - } - - const int recv_buf_len = 65536; - std::vector recv_buf(recv_buf_len); - std::string raw_data; - int recv_ret = 0; - while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) - { - raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); - } - setSocketTimeout(sock, 0); - - if (raw_data.empty()) - { - res.ret_code = -2; - res.err_msg = "no response data"; - return; - } - - size_t json_start = raw_data.find('{'); - size_t json_end = raw_data.rfind('}'); - std::string json_str; - if (json_start == std::string::npos) - { - res.ret_code = -4; - res.err_msg = "json not found in response"; - return; - } - if (json_end == std::string::npos) - { - json_str = raw_data.substr(json_start) + "}"; - } - else - { - json_str = raw_data.substr(json_start, json_end - json_start + 1); - } - - json root; - std::string parse_err; - if (!parseJson(json_str, root, parse_err)) - { - res.ret_code = -3; - res.err_msg = "json parse error: " + parse_err; - return; - } - - res.ret_code = root.value("ret_code", 0); - res.create_on = root.value("create_on", ""); - res.err_msg = root.value("err_msg", ""); - - // 解析站点数组 - if (root.contains("stations") && root["stations"].is_array()) - { - for (auto& item : root["stations"]) - { - StationItem st; - st.id = item.value("id", ""); - st.type = item.value("type", ""); - st.x = item.value("x", 0.0); - st.y = item.value("y", 0.0); - st.r = item.value("r", 0.0); - st.desc = item.value("desc", ""); - st.executor = item.value("executor", ""); - st.prepoint = item.value("prepoint", ""); - st.recfile = item.value("recfile", ""); - - // 兼容 bool 和字符串 "true"/"false" - if (item.contains("spin")) - { - if (item["spin"].is_boolean()) - st.spin = item["spin"].get(); - else if (item["spin"].is_string()) - st.spin = (item["spin"].get() == "true"); - } - if (item.contains("use_down_pgv")) - { - if (item["use_down_pgv"].is_boolean()) - st.use_down_pgv = item["use_down_pgv"].get(); - else if (item["use_down_pgv"].is_string()) - st.use_down_pgv = (item["use_down_pgv"].get() == "true"); - } - - res.stations.push_back(st); - } - } - } - - - // -------------------------------------------------------------------------- - // 1060 - robot_status_current_lock_req 查询当前控制权所有者 - // -------------------------------------------------------------------------- - void AgvSrc1100::getCurrentLockStatus(CurrentLockStatus& info) - { - int sock = sock_status_; - if (sock < 0) - { - info.ret_code = -1; - info.err_msg = "socket not connected"; - return; - } - - // 命令码 0x0424 = 1060,无负载,长度=0 - uint16_t payload_len = 0; - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, - static_cast((payload_len >> 8) & 0xFF), - static_cast(payload_len & 0xFF), - 0x04, 0x24, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - setSocketTimeout(sock, 1000); - - int total_sent = 0; - int retry = 3; - while (total_sent < 16 && retry > 0) - { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, 0); - if (ret <= 0) - { - retry--; - usleep(10000); - continue; - } - total_sent += ret; - } - if (total_sent != 16) - { - setSocketTimeout(sock, 0); - info.ret_code = -2; - info.err_msg = "send frame header failed"; - return; - } - - const int RECV_BUF_LEN = 65536; - std::vector recv_buf(RECV_BUF_LEN); - std::string raw_data; - int recv_ret = 0; - while ((recv_ret = recv(sock, recv_buf.data(), RECV_BUF_LEN, 0)) > 0) - { - raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); - } - setSocketTimeout(sock, 0); - - if (raw_data.empty()) - { - info.ret_code = -2; - info.err_msg = "no response data"; - return; - } - - size_t json_start = raw_data.find('{'); - size_t json_end = raw_data.rfind('}'); - std::string json_str; - if (json_start == std::string::npos) - { - info.ret_code = -4; - info.err_msg = "json not found in response"; - return; - } - if (json_end == std::string::npos) - { - json_str = raw_data.substr(json_start) + "}"; - } - else - { - json_str = raw_data.substr(json_start, json_end - json_start + 1); - } - - json root; - std::string err; - if (!parseJson(json_str, root, err)) - { - info.ret_code = -3; - info.err_msg = "json parse error: " + err; - return; - } - - // 提取控制权信息 - info.locked = root.value("locked", false); - info.ip = root.value("ip", ""); - info.port = root.value("port", 0); - info.type = static_cast(root.value("type", 0)); - info.nick_name = root.value("nick_name", ""); - info.time_t = root.value("time_t", 0LL); - info.desc = root.value("desc", ""); - info.ret_code = root.value("ret_code", 0); - info.create_on = root.value("create_on", ""); - info.err_msg = root.value("err_msg", ""); - } - - - // -------------------------------------------------------------------------- - // 1020 - robot_status_task_req 查询当前导航状态 - // -------------------------------------------------------------------------- - void AgvSrc1100::robotStatusTaskCurrent(RobotStatusTaskCurrentRes& res, const RobotStatusTaskCurrentReq& req) - { - int sock = sock_status_; - LOG(INFO) << "robotStatusTaskCurrent sock fd=" << sock; - if (sock < 0) - { - res.ret_code = -1; - res.err_msg = "status port socket invalid"; - return; - } - - // 组装请求 JSON: {"simple": true/false} - json send_json; - send_json["simple"] = req.simple; - std::string payload = send_json.dump(); - uint16_t payload_len = static_cast(payload.size()); - - // 命令码 0x03FC = 1020 - static uint8_t frame_header[16] = { - 0x5A,0x01,0x00,0x01, - 0x00,0x00, - 0,0, - 0x03,0xFC, - 0,0,0,0,0,0 - }; - frame_header[6] = static_cast((payload_len >> 8) & 0xFF); - frame_header[7] = static_cast(payload_len & 0xFF); - - flushSocket(sock); - setSocketTimeout(sock, 3000); - - // 发送帧头 - int sent = 0; - int retry = 5; - while (sent < 16 && retry > 0) - { - int w = send(sock, frame_header + sent, 16 - sent, MSG_NOSIGNAL); - if (w <= 0) - { - retry--; - usleep(20000); - continue; - } - sent += w; - } - if (sent != 16) - { - res.ret_code = -2; - res.err_msg = "send frame header failed"; - return; - } - - // 分片发送 payload - const int slice = 1024; - size_t off = 0; - size_t total = payload.size(); - const char* data = payload.data(); - while (off < total) - { - int wlen = static_cast(std::min((size_t)slice, total - off)); - int w = send(sock, data + off, wlen, MSG_NOSIGNAL); - if (w <= 0) - { - res.ret_code = -2; - res.err_msg = "send payload failed"; - return; - } - off += w; - } - - // 接收响应 - std::string raw; - uint8_t buf[65536]; - int rlen = 0; - while ((rlen = recv(sock, buf, sizeof(buf), MSG_NOSIGNAL)) > 0) - { - raw.append((char*)buf, rlen); - } - if (raw.empty()) - { - res.ret_code = -2; - res.err_msg = "no response data"; - return; - } - - // 提取 JSON - size_t json_begin = raw.find('{'); - if (json_begin == std::string::npos) - { - res.ret_code = -4; - res.err_msg = "response no json"; - return; - } - std::string json_str = raw.substr(json_begin); - if (json_str.back() != '}') - json_str += "}"; - - json root; - try - { - root = json::parse(json_str); - } - catch (json::parse_error& e) - { - res.ret_code = -3; - res.err_msg = std::string("json parse err:") + e.what(); - return; - } - - // 提取公共字段 - res.ret_code = root.contains("ret_code") ? root["ret_code"].get() : 0; - res.create_on = root.contains("create_on") ? root["create_on"].get() : ""; - res.err_msg = root.contains("err_msg") ? root["err_msg"].get() : ""; - - // 提取导航状态字段 - // task_status: 0=NONE, 1=WAITING, 2=RUNNING, 3=SUSPENDED, 4=COMPLETED, 5=FAILED, 6=CANCELED - res.task_status = root.contains("task_status") ? root["task_status"].get() : 0; - // task_type: 0=无, 1=自由导航到坐标, 2=自由导航到站点, 3=路径导航到站点 - res.task_type = root.contains("task_type") ? root["task_type"].get() : 0; - res.target_id = root.contains("target_id") ? root["target_id"].get() : ""; - res.move_status_info = root.contains("move_status_info") ? root["move_status_info"].get() : ""; - - // 提取目标坐标 [x, y, r] - if (root.contains("target_point") && root["target_point"].is_array()) - { - for (auto& val : root["target_point"]) - res.target_point.push_back(val.get()); - } - - // 提取已经过的站点列表 - if (root.contains("finished_path") && root["finished_path"].is_array()) - { - for (auto& s : root["finished_path"]) - res.finished_path.push_back(s.get()); - } - - // 提取尚未经过的站点列表 - if (root.contains("unfinished_path") && root["unfinished_path"].is_array()) - { - for (auto& s : root["unfinished_path"]) - res.unfinished_path.push_back(s.get()); - } - - // 提取料箱信息 - if (root.contains("containers") && root["containers"].is_array()) - { - for (auto& item : root["containers"]) - { - NavContainerItem c; - c.container_name = item.contains("container_name") ? item["container_name"].get() : ""; - c.desc = item.contains("desc") ? item["desc"].get() : ""; - c.goods_id = item.contains("goods_id") ? item["goods_id"].get() : ""; - c.has_goods = item.contains("has_goods") ? item["has_goods"].get() : false; - res.containers.push_back(c); - } - } - } - - - // -------------------------------------------------------------------------- - // 1110 - robot_status_task_status_package_req 批量查询任务状态 - // -------------------------------------------------------------------------- - void AgvSrc1100::robotStatusTaskPackage(QueryTaskStatusPackageRes& res, const QueryTaskStatusPackageReq& req) - { - int sock = sock_status_; - LOG(INFO) << "robotStatusTaskPackage sock fd=" << sock; - if (sock < 0) - { - res.ret_code = -1; - res.err_msg = "status port socket invalid"; - return; - } - - // 强制携带 task_ids 字段,空数组也生成,避免设备报 60002 错误 - json send_json; - json arr = json::array(); - for (auto& tid : req.task_ids) - arr.push_back(tid); - send_json["task_ids"] = arr; - - std::string payload = send_json.dump(); - uint16_t payload_len = static_cast(payload.size()); - - // 命令码 0x0456 = 1110 - static uint8_t frame_header[16] = { - 0x5A,0x01,0x00,0x01, - 0x00,0x00, - 0,0, - 0x04,0x56, - 0,0,0,0,0,0 - }; - frame_header[6] = static_cast((payload_len >> 8) & 0xFF); - frame_header[7] = static_cast(payload_len & 0xFF); - - flushSocket(sock); - setSocketTimeout(sock, 3000); - - // 发送帧头 - int sent = 0; - int retry = 5; - while (sent < 16 && retry > 0) - { - int w = send(sock, frame_header + sent, 16 - sent, MSG_NOSIGNAL); - if (w <= 0) - { - retry--; - usleep(20000); - continue; - } - sent += w; - } - if (sent != 16) - { - res.ret_code = -2; - res.err_msg = "send frame header failed"; - return; - } - - // 分片发送 payload - const int slice = 1024; - size_t off = 0; - size_t total = payload.size(); - const char* data = payload.data(); - while (off < total) - { - int wlen = static_cast(std::min((size_t)slice, total - off)); - int w = send(sock, data + off, wlen, MSG_NOSIGNAL); - if (w <= 0) - { - res.ret_code = -2; - res.err_msg = "send payload failed"; - return; - } - off += w; - } - - // 接收响应 - std::string raw; - uint8_t buf[65536]; - int rlen = 0; - while ((rlen = recv(sock, buf, sizeof(buf), MSG_NOSIGNAL)) > 0) - { - raw.append((char*)buf, rlen); - } - if (raw.empty()) - { - res.ret_code = -2; - res.err_msg = "no response data"; - return; - } - - // 提取 JSON - size_t json_begin = raw.find('{'); - if (json_begin == std::string::npos) - { - res.ret_code = -4; - res.err_msg = "response no json"; - return; - } - std::string json_str = raw.substr(json_begin); - if (json_str.back() != '}') - json_str += "}"; - - json root; - try - { - root = json::parse(json_str); - } - catch (json::parse_error& e) - { - res.ret_code = -3; - res.err_msg = std::string("json parse err:") + e.what(); - return; - } - - // 提取公共字段 - res.ret_code = root.contains("ret_code") ? root["ret_code"].get() : 0; - res.create_on = root.contains("create_on") ? root["create_on"].get() : ""; - res.err_msg = root.contains("err_msg") ? root["err_msg"].get() : ""; - - // 解析 task_status_package 对象 - if (root.contains("task_status_package")) - { - auto pkg_json = root["task_status_package"]; - auto& pkg_out = res.task_status_package; - - pkg_out.closest_target = pkg_json.contains("closest_target") ? pkg_json["closest_target"].get() : ""; - pkg_out.source_name = pkg_json.contains("source_name") ? pkg_json["source_name"].get() : ""; - pkg_out.target_name = pkg_json.contains("target_name") ? pkg_json["target_name"].get() : ""; - pkg_out.percentage = pkg_json.contains("percentage") ? pkg_json["percentage"].get() : 0.0; - pkg_out.distance = pkg_json.contains("distance") ? pkg_json["distance"].get() : 0.0; - pkg_out.info = pkg_json.contains("info") ? pkg_json["info"].get() : ""; - - // 解析任务状态列表 - if (pkg_json.contains("task_status_list") && pkg_json["task_status_list"].is_array()) - { - for (auto& item : pkg_json["task_status_list"]) - { - SingleTaskStatusItem st; - st.task_id = item.contains("task_id") ? item["task_id"].get() : ""; - st.status = item.contains("status") ? item["status"].get() : 0; - st.type = item.contains("type") ? item["type"].get() : 0; - pkg_out.task_status_list.push_back(st); - } - } - } - } - - - // ============================================================================ - // 端口 19205 – 机器人控制 API(允许 5 个连接) - // 功能:下发控制指令,改变机器人运动或状态 - // ============================================================================ - - // -------------------------------------------------------------------------- - // 2010 - robot_control_motion_req 开环速度运动控制 - // -------------------------------------------------------------------------- - void AgvSrc1100::robotMotionControl(MotionCtrlRes& res, const MotionCtrlReq& req) - { - int sock = sock_control_; - if (sock < 0) - { - res.ret_code = -1; - res.err_msg = "socket not connected"; - return; - } - - // 组装请求 JSON,只包含非零有效字段 - json send_json; - if (req.vx != 0.0) send_json["vx"] = req.vx; // X 方向速度 - if (req.vy != 0.0) send_json["vy"] = req.vy; // Y 方向速度 - if (req.w != 0.0) send_json["w"] = req.w; // 角速度 - if (req.steer != 0.0) send_json["steer"] = req.steer; // 舵角(单舵轮) - if (req.real_steer != 0.0) send_json["real_steer"] = req.real_steer; // 实际舵角 - if (req.duration >= 0) send_json["duration"] = req.duration; // 运动持续时间 - - std::string payload = send_json.dump(); - uint16_t payload_len = static_cast(payload.size()); - - // 命令码 0x07DA = 2010 - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, - static_cast((payload_len >> 8) & 0xFF), - static_cast(payload_len & 0xFF), - 0x07, 0xDA, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - setSocketTimeout(sock, 1000); - - int total_sent = 0; - int retry_cnt = 3; - while (total_sent < 16 && retry_cnt > 0) - { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); - if (ret <= 0) - { - retry_cnt--; - usleep(10000); - continue; - } - total_sent += ret; - } - if (total_sent != 16) - { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send frame header failed"; - return; - } - - // 分片发送 payload - const int slice_size = 4096; - size_t offset = 0; - size_t total_size = payload.size(); - const char* data_ptr = payload.data(); - while (offset < total_size) - { - int send_len = std::min((size_t)slice_size, total_size - offset); - int ret = send(sock, data_ptr + offset, send_len, MSG_NOSIGNAL); - if (ret <= 0) - { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send payload failed"; - return; - } - offset += ret; - usleep(500); - } - - // 接收响应 - const int recv_buf_len = 65536; - std::vector recv_buf(recv_buf_len); - std::string raw_data; - int recv_ret = 0; - while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) - { - raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); - } - setSocketTimeout(sock, 0); - - if (raw_data.empty()) - { - res.ret_code = -2; - res.err_msg = "no response data"; - return; - } - - // 提取并解析 JSON - size_t json_start = raw_data.find('{'); - size_t json_end = raw_data.rfind('}'); - std::string json_str; - if (json_start == std::string::npos) - { - res.ret_code = -4; - res.err_msg = "json not found in response"; - return; - } - if (json_end == std::string::npos) - { - json_str = raw_data.substr(json_start) + "}"; - } - else - { - json_str = raw_data.substr(json_start, json_end - json_start + 1); - } - - json root; - std::string parse_err; - if (!parseJson(json_str, root, parse_err)) - { - res.ret_code = -3; - res.err_msg = "json parse error: " + parse_err; - return; - } - - res.ret_code = root.value("ret_code", 0); - res.create_on = root.value("create_on", ""); - res.err_msg = root.value("err_msg", ""); - } - - - // -------------------------------------------------------------------------- - // 2022 - robot_control_loadmap_req 切换载入地图 - // -------------------------------------------------------------------------- - void AgvSrc1100::robotLoadMap(LoadMapRes& res, const LoadMapReq& req) - { - int sock = sock_control_; - if (sock < 0) - { - res.ret_code = -1; - res.err_msg = "socket not connected"; - return; - } - - // 组装请求 JSON: {"map_name":"xxx"} - json send_json; - send_json["map_name"] = req.map_name; - std::string payload = send_json.dump(); - uint16_t payload_len = static_cast(payload.size()); - - // 命令码 0x07E6 = 2022 - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, - static_cast((payload_len >> 8) & 0xFF), - static_cast(payload_len & 0xFF), - 0x07, 0xE6, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - setSocketTimeout(sock, 1000); - - // 发送帧头 - int total_sent = 0; - int retry_cnt = 3; - while (total_sent < 16 && retry_cnt > 0) - { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); - if (ret <= 0) - { - retry_cnt--; - usleep(10000); - continue; - } - total_sent += ret; - } - if (total_sent != 16) - { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send frame header failed"; - return; - } - - // 分片发送 payload - const int slice_size = 4096; - size_t offset = 0; - size_t total_size = payload.size(); - const char* data_ptr = payload.data(); - while (offset < total_size) - { - int send_len = std::min((size_t)slice_size, total_size - offset); - int ret = send(sock, data_ptr + offset, send_len, MSG_NOSIGNAL); - if (ret <= 0) - { - setSocketTimeout(sock, 3000); - res.ret_code = -2; - res.err_msg = "send payload failed"; - return; - } - offset += ret; - usleep(500); - } - - // 接收响应 - const int recv_buf_len = 65536; - std::vector recv_buf(recv_buf_len); - std::string raw_data; - int recv_ret = 0; - while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) - { - raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); - } - setSocketTimeout(sock, 0); - - if (raw_data.empty()) - { - res.ret_code = -2; - res.err_msg = "no response data"; - return; - } - - // 提取并解析 JSON - size_t json_start = raw_data.find('{'); - size_t json_end = raw_data.rfind('}'); - std::string json_str; - if (json_start == std::string::npos) - { - res.ret_code = -4; - res.err_msg = "json not found in response"; - return; - } - if (json_end == std::string::npos) - { - json_str = raw_data.substr(json_start) + "}"; - } - else - { - json_str = raw_data.substr(json_start, json_end - json_start + 1); - } - - json root; - std::string parse_err; - if (!parseJson(json_str, root, parse_err)) - { - res.ret_code = -3; - res.err_msg = "json parse error: " + parse_err; - return; - } - - res.ret_code = root.value("ret_code", 0); - res.create_on = root.value("create_on", ""); - res.err_msg = root.value("err_msg", ""); - LOG(INFO) << "recv raw_data len=" << raw_data.size() << " content=" << raw_data; - } - - - // -------------------------------------------------------------------------- - // 2000 - robot_control_stop_req 停止开环运动 命令码0x07D0,应答码12000(0x2EE0) - // 下发零速度开环指令,终止当前持续运动 - // -------------------------------------------------------------------------- - // -------------------------------------------------------------------------- - // 2000 - robot_control_stop_req 停止开环运动 命令码0x07D0,应答码12000(0x2EE0) - // 下发零速度开环指令,终止当前持续运动 - // -------------------------------------------------------------------------- - - void AgvSrc1100::robotControlStop(RobotControlStopRes& res) - { - int sock = sock_control_; - if (sock < 0) { - res.ret_code = -1; - res.err_msg = "control socket disconnected"; - return; - } - - // 业务负载长度为 0 - uint32_t payload_len = 0; - - // 标准 16 字节帧头(长度 4 字节,命令码 0x07D0,保留 6 字节) - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - static_cast((payload_len >> 24) & 0xFF), - static_cast((payload_len >> 16) & 0xFF), - static_cast((payload_len >> 8) & 0xFF), - static_cast(payload_len & 0xFF), - 0x07, 0xD0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - setSocketTimeout(sock, 1000); // 适当增加超时 - - // 发送帧头(重试机制) - int total_sent = 0; - int retry = 3; - while (total_sent < 16 && retry-- > 0) { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); - if (ret <= 0) { - usleep(10000); - continue; - } - total_sent += ret; - } - if (total_sent != 16) { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send header failed"; - return; - } - - // 无 payload,无需发送额外数据 - - // 接收响应(使用较大缓冲区) - const int RECV_BUF = 131072; - std::vector recv_buf(RECV_BUF); - std::string raw_data; - int rlen; - while ((rlen = recv(sock, recv_buf.data(), RECV_BUF, MSG_NOSIGNAL)) > 0) { - raw_data.append(reinterpret_cast(recv_buf.data()), rlen); - } - setSocketTimeout(sock, 0); - - if (raw_data.empty()) { - res.ret_code = -2; - res.err_msg = "no response from device"; - return; - } - - // 提取 JSON(容错补全) - size_t json_start = raw_data.find('{'); - if (json_start == std::string::npos) { - res.ret_code = -4; - res.err_msg = "no JSON in response"; - return; - } - std::string json_str = raw_data.substr(json_start); - // 补全缺失的 '}' - int left = std::count(json_str.begin(), json_str.end(), '{'); - int right = std::count(json_str.begin(), json_str.end(), '}'); - while (right < left) { json_str += "}"; right++; } - - json root; - std::string parse_err; - if (!parseJson(json_str, root, parse_err)) { - res.ret_code = -3; - res.err_msg = "JSON parse error: " + parse_err; - return; - } - - res.ret_code = root.value("ret_code", 0); - res.create_on = root.value("create_on", ""); - res.err_msg = root.value("err_msg", ""); - } - - - // ============================================================================ - // 端口 19206 – 机器人导航 API(允许 5 个连接) - // 功能:下发导航任务 - // ============================================================================ - - // -------------------------------------------------------------------------- - // 3066 - robot_task_gotargetlist_req 指定路径导航(多点) - // 功能:下发多段连续站点导航任务序列,任务串行排队执行 - // 必填字段:task_id / source_id / id - // -------------------------------------------------------------------------- - void AgvSrc1100::robotGoTargetList(GoTargetListRes& res, const GoTargetListReq& req) - { - int sock = sock_nav_; - LOG(INFO) << "robotGoTargetList sock fd=" << sock; - if (sock < 0) - { - res.ret_code = -1; - res.err_msg = "nav socket invalid"; - return; - } - - // 组装请求 JSON: {"move_task_list": [{"task_id":"xxx","source_id":"xxx","id":"xxx"}, ...]} - json send_json; - json arr = json::array(); - for (const auto& task : req.move_task_list) - { - json obj; - obj["task_id"] = task.task_id; - obj["source_id"] = task.source_id; - obj["id"] = task.id; - if (!task.operation.empty()) - { - obj["operation"] = task.operation; - obj["jack_height"] = task.jack_height; - } - arr.push_back(obj); - } - send_json["move_task_list"] = arr; - std::string payload = send_json.dump(); - uint16_t payload_len = static_cast(payload.size()); - LOG(INFO) << "nav payload length:" << payload_len; - - // 命令码 0x0BFA = 3066 - static uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, - 0,0, - 0x0B, 0xFA, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - send_buf[6] = static_cast((payload_len >> 8) & 0xFF); - send_buf[7] = static_cast(payload_len & 0xFF); - - flushSocket(sock); - setSocketTimeout(sock, 3000); - - // 发送帧头,带重试 - int total_sent = 0; - int retry_cnt = 5; - while (total_sent < 16 && retry_cnt > 0) - { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); - if (ret <= 0) - { - LOG(ERROR) << "send header failed, ret=" << ret - << ", errno=" << errno - << " (" << strerror(errno) << ")"; - retry_cnt--; - usleep(20000); - continue; - } - total_sent += ret; - } - if (total_sent != 16) - { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send frame header failed"; - return; - } - - // 分片发送 payload(1024 字节一片) - const int slice_size = 1024; - size_t offset = 0; - size_t total_size = payload.size(); - const char* data_ptr = payload.data(); - while (offset < total_size) - { - int send_len = std::min((size_t)slice_size, total_size - offset); - int ret = send(sock, data_ptr + offset, send_len, MSG_NOSIGNAL); - if (ret <= 0) - { - LOG(ERROR) << "send payload failed, errno=" << errno << " (" << strerror(errno) << ")"; - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send payload failed"; - return; - } - offset += ret; - } - - // 接收响应 - const int recv_buf_len = 65536; - std::vector recv_buf(recv_buf_len); - std::string raw_data; - int recv_ret = 0; - while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, MSG_NOSIGNAL)) > 0) - { - raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); - } - setSocketTimeout(sock, 0); - - if (raw_data.empty()) - { - res.ret_code = -2; - res.err_msg = "no response data"; - return; - } - - // 提取并解析 JSON - size_t json_start = raw_data.find('{'); - if (json_start == std::string::npos) - { - res.ret_code = -4; - res.err_msg = "json not found in response"; - return; - } - std::string json_str = raw_data.substr(json_start); - if (json_str.back() != '}') - { - json_str += "}"; - } - - json root; - std::string parse_err; - try - { - root = json::parse(json_str); - } - catch (json::parse_error& e) - { - res.ret_code = -3; - res.err_msg = "json parse error: " + std::string(e.what()); - return; - } - - res.ret_code = root.contains("ret_code") ? root["ret_code"].get() : 0; - res.create_on = root.contains("create_on") ? root["create_on"].get() : ""; - res.err_msg = root.contains("err_msg") ? root["err_msg"].get() : ""; - } - - - // -------------------------------------------------------------------------- - // 3051 - robot_task_gotarget_req 单点路径导航(自动规划) - // 功能:给定目标站点,机器人自动规划路径前往 - // 注意:仅用于单车测试,严禁用于多车调度场景 - // -------------------------------------------------------------------------- - void AgvSrc1100::robotGoTarget(RobotGoTargetRes& res, const RobotGoTargetReq& req) - { - int sock = sock_nav_; - LOG(INFO) << "robotGoTarget control sock fd=" << sock; - if (sock < 0) - { - res.ret_code = -1; - res.err_msg = "control port socket invalid"; - return; - } - - // 组装请求 JSON(根据协议 3051 的字段结构) - json send_json; - // 必填字段 - send_json["source_id"] = req.source_id; - send_json["id"] = req.id; - if (!req.task_id.empty()) - send_json["task_id"] = req.task_id; - - // 基础运动参数(只添加非默认值) - if (req.angle != 0) send_json["angle"] = req.angle; - if (!req.method.empty()) send_json["method"] = req.method; - if (req.max_speed != 0) send_json["max_speed"] = req.max_speed; - if (req.max_wspeed != 0) send_json["max_wspeed"] = req.max_wspeed; - if (req.max_acc != 0) send_json["max_acc"] = req.max_acc; - if (req.max_wacc != 0) send_json["max_wacc"] = req.max_wacc; - if (req.duration > 0) send_json["duration"] = req.duration; - if (req.orientation != 0) send_json["orientation"] = req.orientation; - if (req.spin) send_json["spin"] = true; - if (req.delay > 0) send_json["delay"] = req.delay; - if (req.start_rot_dir != 0) send_json["start_rot_dir"] = req.start_rot_dir; - if (req.end_rot_dir != 0) send_json["end_rot_dir"] = req.end_rot_dir; - if (req.reach_dist != 0) send_json["reach_dist"] = req.reach_dist; - if (req.reach_angle != 0) send_json["reach_angle"] = req.reach_angle; - if (!req.skill_name.empty()) send_json["skill_name"] = req.skill_name; - - // PGV 二次定位参数 - auto& pgv = req.pgv; - if (pgv.use_pgv) send_json["use_pgv"] = true; - if (pgv.use_down_pgv) send_json["use_down_pgv"] = true; - if (pgv.pgv_adjust_dist != 0) send_json["pgv_adjust_dist"] = pgv.pgv_adjust_dist; - if (pgv.pgv_adjust_cx != 0) send_json["pgv_adjust_cx"] = pgv.pgv_adjust_cx; - if (pgv.pgv_adjust_cy != 0) send_json["pgv_adjust_cy"] = pgv.pgv_adjust_cy; - if (pgv.pgv_x_adjust != 0) send_json["pgv_x_adjust"] = pgv.pgv_x_adjust; - - // 设备操作(顶升/货叉/辊筒/牵引/音频/等待DI/设置DO/脚本) - if (!req.operation.empty()) - { - send_json["operation"] = req.operation; - - // 顶升设备 JackLoad/JackUnload/JackHeight - if (req.operation.substr(0,4) == "Jack") - { - if (req.jack_height != 0) send_json["jack_height"] = req.jack_height; - } - // 货叉设备 ForkLoad/ForkUnload/ForkHeight/ForkForward - else if (req.operation.substr(0,4) == "Fork") - { - if (req.start_height != 0) send_json["start_height"] = req.start_height; - if (req.end_height != 0) send_json["end_height"] = req.end_height; - if (req.fork_mid_height != 0) send_json["fork_mid_height"] = req.fork_mid_height; - if (req.fork_dist != 0) send_json["fork_dist"] = req.fork_dist; - } - // 辊筒设备 RollerLoad/RollerUnload/RollerRoll/... - else if (req.operation.substr(0,6) == "Roller") - { - if (!req.direction.empty()) send_json["direction"] = req.direction; - } - // 牵引设备 HookLoad/HookUnload - else if (req.operation.substr(0,4) == "Hook") - { - if (req.recognize) send_json["recognize"] = true; - } - // 音频设备 sound - else if (req.operation == "sound") - { - json sound; - auto& sa = req.sounds_args; - if (!sa.name.empty()) sound["name"] = sa.name; - if (sa.loop != 0) sound["loop"] = sa.loop; - if (sa.stop != 0) sound["stop"] = sa.stop; - send_json["sounds_args"] = sound; - } - // 等待 DI 触发 - else if (req.operation == "WaitDI") - { - json args_obj; - json di_arr = json::array(); - for (auto& di : req.wait_di_args.DI) - { - json di_item; - di_item["id"] = di.id; - di_item["status"] = di.status; - di_arr.push_back(di_item); - } - args_obj["DI"] = di_arr; - if (req.wait_di_args.timeout > 0) args_obj["timeout"] = req.wait_di_args.timeout; - send_json["args"] = args_obj; - } - // 控制 DO 开关 - else if (req.operation == "SetDO") - { - json args_obj; - json do_arr = json::array(); - for (auto& d : req.set_do_args.DO) - { - json do_item; - do_item["id"] = d.id; - do_item["status"] = d.status; - do_arr.push_back(do_item); - } - args_obj["DO"] = do_arr; - send_json["args"] = args_obj; - } - } - - // 视觉识别标志 - if (req.recognize) send_json["recognize"] = true; - if (!req.recfile.empty()) send_json["recfile"] = req.recfile; - - // 脚本执行 - if (!req.script_name.empty()) - { - send_json["script_name"] = req.script_name; - send_json["script_stage"] = req.script_stage; - json s_arg; - for (auto& kv : req.script_args.str_kv) s_arg[kv.first] = kv.second; - for (auto& kv : req.script_args.num_kv) s_arg[kv.first] = kv.second; - json do_arr = json::array(); - for (auto& d : req.script_args.do_list) - { - json o; o["id"] = d.id; o["status"] = d.status; do_arr.push_back(o); - } - if (!do_arr.empty()) s_arg["DO"] = do_arr; - json di_arr = json::array(); - for (auto& di : req.script_args.di_list) - { - json o; o["id"] = di.id; o["status"] = di.status; di_arr.push_back(o); - } - if (!di_arr.empty()) s_arg["DI"] = di_arr; - send_json["script_args"] = s_arg; - } - - // 原地旋转(GoByOdometer) - if (req.move_angle != 0) - { - send_json["move_angle"] = req.move_angle; - send_json["speed_w"] = req.speed_w; - send_json["loc_mode"] = req.loc_mode; - } - - // 自由导航(仅双轮差速底盘) - if (req.freeGo.x != 0 || req.freeGo.y != 0) - { - json fg; - fg["x"] = req.freeGo.x; - fg["y"] = req.freeGo.y; - fg["theta"] = req.freeGo.theta; - send_json["freeGo"] = fg; - } - - std::string payload = send_json.dump(); - uint16_t payload_len = static_cast(payload.size()); - - // 命令码 0x0BEB = 3051 - uint8_t frame_header[16] = { - 0x5A,0x01,0x00,0x01, - 0x00,0x00, - 0,0, - 0x0B,0xEB, - 0,0,0,0,0,0 - }; - frame_header[6] = static_cast((payload_len >> 8) & 0xFF); - frame_header[7] = static_cast(payload_len & 0xFF); - - flushSocket(sock); - setSocketTimeout(sock, 3000); - - // 发送帧头 - int sent = 0; - int retry = 5; - while (sent < 16 && retry > 0) - { - int w = send(sock, frame_header + sent, 16 - sent, MSG_NOSIGNAL); - if (w <= 0) - { - retry--; - usleep(20000); - continue; - } - sent += w; - } - if (sent != 16) - { - res.ret_code = -2; - res.err_msg = "send frame header failed"; - return; - } - - // 分片发送 payload - const int slice = 1024; - size_t off = 0; - size_t total = payload.size(); - const char* data = payload.data(); - while (off < total) - { - int wlen = static_cast(std::min((size_t)slice, total - off)); - int w = send(sock, data + off, wlen, MSG_NOSIGNAL); - if (w <= 0) - { - res.ret_code = -2; - res.err_msg = "send payload failed"; - return; - } - off += w; - } - - // 接收响应 - std::string raw; - uint8_t buf[65536]; - int rlen = 0; - while ((rlen = recv(sock, buf, sizeof(buf), MSG_NOSIGNAL)) > 0) - { - raw.append((char*)buf, rlen); - } - if (raw.empty()) - { - res.ret_code = -2; - res.err_msg = "no response data"; - return; - } - - // 提取并解析 JSON - size_t json_start = raw.find('{'); - if (json_start == std::string::npos) - { - res.ret_code = -4; - res.err_msg = "response no valid json"; - return; - } - std::string json_str = raw.substr(json_start); - if (json_str.back() != '}') json_str += "}"; - - json root; - try - { - root = json::parse(json_str); - } - catch (json::parse_error& e) - { - res.ret_code = -3; - res.err_msg = std::string("json parse err:") + e.what(); - return; - } - - res.ret_code = root.contains("ret_code") ? root["ret_code"].get() : 0; - res.create_on = root.contains("create_on") ? root["create_on"].get() : ""; - res.err_msg = root.contains("err_msg") ? root["err_msg"].get() : ""; - } - - - // ============================================================================ - // 端口 19207 – 机器人配置 API(允许 5 个连接) - // 功能:配置类操作(地图上传下载、控制权管理等) - // ============================================================================ - - // -------------------------------------------------------------------------- - // 4005 - robot_config_lock_req 抢占控制权 - // 功能:向机器人申请独占控制权,成功后获得操作权限 - // -------------------------------------------------------------------------- - void AgvSrc1100::lockRobotControl(LockResult& info, const std::string& nick_name) - { - int sock = sock_config_; - if (sock < 0) - { - info.ret_code = -1; - info.err_msg = "socket not connected"; - return; - } - - // 组装请求 JSON: {"nick_name":"xxx"} - std::string req_json = R"({"nick_name":")" + nick_name + R"("})"; - uint16_t payload_len = static_cast(req_json.size()); - - // 命令码 0x0FA5 = 4005 - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - 0x00, 0x00, - static_cast((payload_len >> 8) & 0xFF), - static_cast(payload_len & 0xFF), - 0x0F, 0xA5, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - setSocketTimeout(sock, 1000); - - // 发送帧头 - int total_sent = 0; - int retry = 3; - while (total_sent < 16 && retry > 0) - { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, 0); - if (ret <= 0) - { - retry--; - usleep(10000); - continue; - } - total_sent += ret; - } - if (total_sent != 16) - { - setSocketTimeout(sock, 0); - info.ret_code = -2; - info.err_msg = "send frame header failed"; - return; - } - - // 分片发送 JSON 负载 - const int slice_size = 4096; - size_t offset = 0; - size_t total_size = req_json.size(); - const char* data = req_json.c_str(); - while (offset < total_size) - { - int send_len = std::min((size_t)slice_size, total_size - offset); - int ret = send(sock, data + offset, send_len, 0); - if (ret <= 0) - { - setSocketTimeout(sock, 0); - info.ret_code = -2; - info.err_msg = "send payload failed"; - return; - } - offset += ret; - usleep(500); - } - - // 接收响应 - const int recv_buf_len = 65536; - std::vector recv_buf(recv_buf_len); - std::string raw_data; - int recv_ret = 0; - while ((recv_ret = recv(sock, recv_buf.data(), recv_buf_len, 0)) > 0) - { - raw_data.append(reinterpret_cast(recv_buf.data()), recv_ret); - } - setSocketTimeout(sock, 0); - - if (raw_data.empty()) - { - info.ret_code = -2; - info.err_msg = "no response data"; - return; - } - - // 提取并解析 JSON - size_t json_start = raw_data.find('{'); - size_t json_end = raw_data.rfind('}'); - std::string json_str; - if (json_start == std::string::npos) - { - info.ret_code = -4; - info.err_msg = "json not found in response"; - return; - } - if (json_end == std::string::npos) - { - json_str = raw_data.substr(json_start) + "}"; - } - else - { - json_str = raw_data.substr(json_start, json_end - json_start + 1); - } - - json root; - std::string err; - if (!parseJson(json_str, root, err)) - { - info.ret_code = -3; - info.err_msg = "json parse error: " + err; - return; - } - - info.ret_code = root.value("ret_code", 0); - info.create_on = root.value("create_on", ""); - info.err_msg = root.value("err_msg", ""); - } - - - - // ============================================================================ - // 3003 - robot_task_cancel_req 取消当前导航任务 (导航端口 19206) - // ============================================================================ - void AgvSrc1100::robotTaskCancel(RobotTaskCancelRes& res) { - int sock = sock_nav_; - if (sock < 0) { - res.ret_code = -1; - res.err_msg = "nav socket disconnected"; - return; - } - - uint32_t payload_len = 0; - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - static_cast((payload_len >> 24) & 0xFF), - static_cast((payload_len >> 16) & 0xFF), - static_cast((payload_len >> 8) & 0xFF), - static_cast(payload_len & 0xFF), - 0x0B, 0xBB, // 命令码 3003 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - setSocketTimeout(sock, 1000); - - int total_sent = 0; - int retry = 3; - while (total_sent < 16 && retry-- > 0) { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); - if (ret <= 0) { - usleep(10000); - continue; - } - total_sent += ret; - } - if (total_sent != 16) { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send header failed"; - return; - } - - // 接收响应(先读 16 字节头,再按长度读 JSON) - uint8_t recv_header[16]; - size_t header_read = 0; - while (header_read < 16) { - int ret = recv(sock, recv_header + header_read, 16 - header_read, MSG_NOSIGNAL); - if (ret <= 0) { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "recv header failed or timeout"; - return; - } - header_read += ret; - } - - uint32_t data_len = (recv_header[4] << 24) | - (recv_header[5] << 16) | - (recv_header[6] << 8) | - recv_header[7]; - - std::string json_str; - if (data_len > 0) { - json_str.resize(data_len); - size_t total_read = 0; - while (total_read < data_len) { - int ret = recv(sock, &json_str[total_read], data_len - total_read, MSG_NOSIGNAL); - if (ret <= 0) { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "recv payload failed or timeout"; - return; - } - total_read += ret; - } - } - setSocketTimeout(sock, 0); - - if (json_str.empty()) { - res.ret_code = -4; - res.err_msg = "empty json data"; - return; - } - - // 容错提取 JSON - size_t json_start = json_str.find('{'); - if (json_start == std::string::npos) { - res.ret_code = -4; - res.err_msg = "no JSON found in payload"; - return; - } - size_t json_end = json_str.rfind('}'); - if (json_end != std::string::npos) { - json_str = json_str.substr(json_start, json_end - json_start + 1); - } else { - json_str = json_str.substr(json_start) + "}"; - } - - json root; - std::string parse_err; - if (!parseJson(json_str, root, parse_err)) { - res.ret_code = -3; - res.err_msg = "JSON parse error: " + parse_err; - return; - } - - res.ret_code = root.value("ret_code", 0); - res.create_on = root.value("create_on", ""); - res.err_msg = root.value("err_msg", ""); - - LOG(INFO) << "robotTaskCancel success, ret_code=" << res.ret_code; - } - - // ============================================================================ - // 3002 - robot_task_resume_req 继续当前导航任务 (导航端口 19206) - // ============================================================================ - void AgvSrc1100::robotTaskResume(RobotTaskResumeRes& res) { - int sock = sock_nav_; - if (sock < 0) { - res.ret_code = -1; - res.err_msg = "nav socket disconnected"; - return; - } - - uint32_t payload_len = 0; - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - static_cast((payload_len >> 24) & 0xFF), - static_cast((payload_len >> 16) & 0xFF), - static_cast((payload_len >> 8) & 0xFF), - static_cast(payload_len & 0xFF), - 0x0B, 0xBA, // 命令码 3002 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - setSocketTimeout(sock, 1000); - - // 发送帧头(带重试) - int total_sent = 0; - int retry = 3; - while (total_sent < 16 && retry-- > 0) { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); - if (ret <= 0) { - usleep(10000); - continue; - } - total_sent += ret; - } - if (total_sent != 16) { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send header failed"; - return; - } - - // 接收响应(先读 16 字节头,再按长度读 JSON) - uint8_t recv_header[16]; - size_t header_read = 0; - while (header_read < 16) { - int ret = recv(sock, recv_header + header_read, 16 - header_read, MSG_NOSIGNAL); - if (ret <= 0) { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "recv header failed or timeout"; - return; - } - header_read += ret; - } - - uint32_t data_len = (recv_header[4] << 24) | - (recv_header[5] << 16) | - (recv_header[6] << 8) | - recv_header[7]; - - std::string json_str; - if (data_len > 0) { - json_str.resize(data_len); - size_t total_read = 0; - while (total_read < data_len) { - int ret = recv(sock, &json_str[total_read], data_len - total_read, MSG_NOSIGNAL); - if (ret <= 0) { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "recv payload failed or timeout"; - return; - } - total_read += ret; - } - } - setSocketTimeout(sock, 0); - - if (json_str.empty()) { - res.ret_code = -4; - res.err_msg = "empty json data"; - return; - } - - // 容错提取 JSON - size_t json_start = json_str.find('{'); - if (json_start == std::string::npos) { - res.ret_code = -4; - res.err_msg = "no JSON found in payload"; - return; - } - size_t json_end = json_str.rfind('}'); - if (json_end != std::string::npos) { - json_str = json_str.substr(json_start, json_end - json_start + 1); - } else { - json_str = json_str.substr(json_start) + "}"; - } - - json root; - std::string parse_err; - if (!parseJson(json_str, root, parse_err)) { - res.ret_code = -3; - res.err_msg = "JSON parse error: " + parse_err; - return; - } - - res.ret_code = root.value("ret_code", 0); - res.create_on = root.value("create_on", ""); - res.err_msg = root.value("err_msg", ""); - - LOG(INFO) << "robotTaskResume success, ret_code=" << res.ret_code; - } - - - - // ============================================================================ - // 3001 - robot_task_pause_req 暂停当前导航任务 (控制端口 19205) - // ============================================================================ - void AgvSrc1100::robotTaskPause(RobotTaskPauseRes& res) { - int sock = sock_nav_; // 使用控制端口 - if (sock < 0) { - res.ret_code = -1; - res.err_msg = "control socket disconnected"; - return; - } - - // 业务负载长度为 0(无 JSON 数据) - uint32_t payload_len = 0; - - // 标准 16 字节帧头(长度 4 字节,命令码 0x0BB9,保留 6 字节) - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x01, - static_cast((payload_len >> 24) & 0xFF), - static_cast((payload_len >> 16) & 0xFF), - static_cast((payload_len >> 8) & 0xFF), - static_cast(payload_len & 0xFF), - 0x0B, 0xB9, // 命令码 3001 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - setSocketTimeout(sock, 1000); // 安全超时 - - // 发送帧头(带重试) - int total_sent = 0; - int retry = 3; - while (total_sent < 16 && retry-- > 0) { - int ret = send(sock, send_buf + total_sent, 16 - total_sent, MSG_NOSIGNAL); - if (ret <= 0) { - usleep(10000); - continue; - } - total_sent += ret; - } - if (total_sent != 16) { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "send header failed"; - return; - } - - // 接收响应(先读 16 字节帧头,再按长度读 JSON) - uint8_t recv_header[16]; - size_t header_read = 0; - while (header_read < 16) { - int ret = recv(sock, recv_header + header_read, 16 - header_read, MSG_NOSIGNAL); - if (ret <= 0) { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "recv header failed or timeout"; - return; - } - header_read += ret; - } - - // 解析数据区长度(大端 4 字节) - uint32_t data_len = (recv_header[4] << 24) | - (recv_header[5] << 16) | - (recv_header[6] << 8) | - recv_header[7]; - - // 读取数据区(JSON) - std::string json_str; - if (data_len > 0) { - json_str.resize(data_len); - size_t total_read = 0; - while (total_read < data_len) { - int ret = recv(sock, &json_str[total_read], data_len - total_read, MSG_NOSIGNAL); - if (ret <= 0) { - setSocketTimeout(sock, 0); - res.ret_code = -2; - res.err_msg = "recv payload failed or timeout"; - return; - } - total_read += ret; - } - } - setSocketTimeout(sock, 0); - - if (json_str.empty()) { - res.ret_code = -4; - res.err_msg = "empty json data"; - return; - } - - // 容错提取 JSON(可能包含前缀/后缀) - size_t json_start = json_str.find('{'); - if (json_start == std::string::npos) { - res.ret_code = -4; - res.err_msg = "no JSON found in payload"; - return; - } - size_t json_end = json_str.rfind('}'); - if (json_end != std::string::npos) { - json_str = json_str.substr(json_start, json_end - json_start + 1); - } else { - json_str = json_str.substr(json_start) + "}"; - } - - // 解析 JSON - json root; - std::string parse_err; - if (!parseJson(json_str, root, parse_err)) { - res.ret_code = -3; - res.err_msg = "JSON parse error: " + parse_err; - return; - } - - res.ret_code = root.value("ret_code", 0); - res.create_on = root.value("create_on", ""); - res.err_msg = root.value("err_msg", ""); - - LOG(INFO) << "robotTaskPause success, ret_code=" << res.ret_code; - } - - - - - - // -------------------------------------------------------------------------- - // 4010 - robot_config_uploadmap_req 上传地图 - // 功能:将地图文件上传到机器人 - // -------------------------------------------------------------------------- - void AgvSrc1100::uploadMap(UploadMapResult& info, const std::string& map_json) - { - int sock = sock_config_; - if (sock < 0) - { - info.ret_code = -1; - info.err_msg = "socket not connected"; - return; - } - - // 清空残留数据,设置超时 - flushSocket(sock); - setSocketTimeout(sock, 1000); - - // 计算负载长度(地图 JSON 数据) - uint16_t payload_len = static_cast(map_json.size()); - // 命令码 0x0FAA = 4010 - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x00, - 0x00, 0x00, - static_cast((payload_len >> 8) & 0xFF), - static_cast(payload_len & 0xFF), - 0x0F, 0xAA, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - // 发送帧头(带重试) - int totalSent = 0; - int retries = 3; - while (totalSent < 16 && retries-- > 0) - { - int ret = send(sock, send_buf + totalSent, 16 - totalSent, 0); - if (ret <= 0) - { - usleep(10000); - continue; - } - totalSent += ret; - } - if (totalSent != 16) - { - setSocketTimeout(sock, 0); - info.ret_code = -2; - info.err_msg = "send frame header failed"; - return; - } - - // 分片发送地图数据(4096 字节一片) - const int SLICE_SIZE = 4096; - size_t offset = 0; - size_t totalSize = map_json.size(); - const char* data = map_json.c_str(); - - while (offset < totalSize) - { - int sendLen = std::min((size_t)SLICE_SIZE, totalSize - offset); - int ret = send(sock, data + offset, sendLen, 0); - if (ret <= 0) - { - setSocketTimeout(sock, 0); - info.ret_code = -2; - info.err_msg = "send map payload failed"; - return; - } - offset += ret; - usleep(500); // 短暂延时,防止发送过快 - } - - // 接收响应 - const int RECV_BUF_LEN = 65536; - std::vector recvBuf(RECV_BUF_LEN); - std::string rawData; - int recvRet = 0; - while ((recvRet = recv(sock, recvBuf.data(), RECV_BUF_LEN, 0)) > 0) - { - rawData.append(reinterpret_cast(recvBuf.data()), recvRet); - } - setSocketTimeout(sock, 0); - - if (rawData.empty()) - { - info.ret_code = -2; - info.err_msg = "no response data"; - return; - } - - // 提取并解析 JSON - size_t jsonStart = rawData.find('{'); - size_t jsonEnd = rawData.rfind('}'); - std::string json_str; - if (jsonStart == std::string::npos) - { - info.ret_code = -4; - info.err_msg = "json not found"; - return; - } - if (jsonEnd == std::string::npos) - { - json_str = rawData.substr(jsonStart) + "}"; - } - else - { - json_str = rawData.substr(jsonStart, jsonEnd - jsonStart + 1); - } - - json root; - std::string err; - if (!parseJson(json_str, root, err)) - { - info.ret_code = -3; - info.err_msg = "json parse error: " + err; - return; - } - - info.ret_code = root.value("ret_code", 0); - info.create_on = root.value("create_on", ""); - info.err_msg = root.value("err_msg", ""); - } - - - // -------------------------------------------------------------------------- - // 4011 - robot_config_downloadmap_req 下载地图 - // 功能:从机器人下载指定地图文件 - // -------------------------------------------------------------------------- - void AgvSrc1100::downloadMap(DownloadMapResult& info, const std::string& map_name) - { - int sock = sock_config_; - if (sock < 0) - { - info.ret_code = -1; - info.err_msg = "socket not connected"; - return; - } - - // 组装请求 JSON: {"map_name":"xxx"} - std::string req_json = R"({"map_name":")" + map_name + R"("})"; - uint8_t payload_len = static_cast(req_json.size()); - - // 命令码 0x0FAB = 4011 - uint8_t send_buf[16] = { - 0x5A, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, payload_len, - 0x0F, 0xAB, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - flushSocket(sock); - - // 发送帧头 - if (send(sock, send_buf, 16, MSG_NOSIGNAL) != 16) - { - info.ret_code = -2; - info.err_msg = "send frame header failed"; - return; - } - // 发送 JSON 负载 - if (send(sock, req_json.data(), req_json.size(), MSG_NOSIGNAL) != (ssize_t)req_json.size()) - { - info.ret_code = -2; - info.err_msg = "send payload failed"; - return; - } - - // 接收地图数据(地图文件可能较大,使用 65536 字节缓冲区) - const int RECV_BUF_LEN = 65536; - std::vector recvBuf(RECV_BUF_LEN); - std::string rawData; - int ret = 0; - - setSocketTimeout(sock, 500); // 延长超时适配大地图 - while ((ret = recv(sock, recvBuf.data(), RECV_BUF_LEN, 0)) > 0) - { - rawData.append(reinterpret_cast(recvBuf.data()), ret); - } - setSocketTimeout(sock, 0); - - if (rawData.empty()) - { - info.ret_code = -2; - info.err_msg = "no response data"; - return; - } - - // 提取并解析 JSON - size_t jsonStart = rawData.find('{'); - size_t jsonEnd = rawData.rfind('}'); - std::string json_str; - - if (jsonStart == std::string::npos) - { - info.ret_code = -4; - info.err_msg = "json not found"; - return; - } - if (jsonEnd == std::string::npos) - { - json_str = rawData.substr(jsonStart) + "}"; - } - else - { - json_str = rawData.substr(jsonStart, jsonEnd - jsonStart + 1); - } - - json root; - std::string err; - if (!parseJson(json_str, root, err)) - { - info.ret_code = -3; - info.err_msg = err; - return; - } - - info.map_name = map_name; - info.ret_code = root.value("ret_code", 0); - info.create_on = root.value("create_on", ""); - info.err_msg = root.value("err_msg", ""); - // 成功时保存地图内容 - info.map_content = (0 == info.ret_code) ? json_str : ""; - } - - - // ============================================================================ - // 端口 19210 – 其他 API(允许 5 个连接) - // 功能:外设控制(顶升/货叉/辊筒/音频/IO 等) - // 当前未实现具体方法,预留扩展 - // ============================================================================ - - - // ============================================================================ - // 端口 19301 – 机器人推送 API(允许 10 个连接) - // 功能:接收机器人主动推送的实时状态数据 - // 当前未实现具体方法,预留扩展 - // ============================================================================ - - - - -} \ No newline at end of file diff --git a/cmvr-es/devices/biohead/abstract_biohead.h b/cmvr-es/devices/biohead/abstract_biohead.h index 09b774ff..93e0eb20 100644 --- a/cmvr-es/devices/biohead/abstract_biohead.h +++ b/cmvr-es/devices/biohead/abstract_biohead.h @@ -58,7 +58,6 @@ namespace cmvr::device { // 抽象头部类 class AbstractBiohead : public AbstractDevice { public: - AbstractBiohead() = default; explicit AbstractBiohead(const XmlNode &config) : AbstractDevice(config) {} ~AbstractBiohead() override = default; diff --git a/cmvr-es/devices/biohead/biohead_esp32/include/biohead_esp32.h b/cmvr-es/devices/biohead/biohead_esp32/include/biohead_esp32.h index 08c0953f..52b34d5d 100644 --- a/cmvr-es/devices/biohead/biohead_esp32/include/biohead_esp32.h +++ b/cmvr-es/devices/biohead/biohead_esp32/include/biohead_esp32.h @@ -7,7 +7,7 @@ #include #include #include -#include "cmvr/config/biohead_config/biohead_config.pb.h" + namespace cmvr::device { struct ServoChannel { @@ -18,7 +18,6 @@ namespace cmvr::device { class BioHeadRobot : public AbstractBiohead { public: explicit BioHeadRobot(const XmlNode &config); - BioHeadRobot(const cmvr::config::BioHeadConfig& config); ~BioHeadRobot() override = default; // 接口实现 @@ -67,7 +66,7 @@ namespace cmvr::device { std::shared_ptr speak_thread_; std::atomic speak_running_{false}; - cmvr::config::BioHeadConfig config_; + }; diff --git a/cmvr-es/devices/biohead/biohead_esp32/src/biohead_esp32.cpp b/cmvr-es/devices/biohead/biohead_esp32/src/biohead_esp32.cpp index 8ff9e3b3..c3b67887 100644 --- a/cmvr-es/devices/biohead/biohead_esp32/src/biohead_esp32.cpp +++ b/cmvr-es/devices/biohead/biohead_esp32/src/biohead_esp32.cpp @@ -26,219 +26,6 @@ BioHeadRobot::BioHeadRobot(const XmlNode &config) : AbstractBiohead(config) { } -BioHeadRobot::BioHeadRobot(const cmvr::config::BioHeadConfig& config) : config_(config) { - // 从config中读取串口参数 - port_name_ = config_.serial(); - - serial_ = std::make_shared(); - if (!serial_->open(port_name_, 115200)) { - LOG(ERROR) << "[BioHeadRobot] Failed to open serial port: " << port_name_; - } else { - serial_->wakeupESP32(port_name_); - LOG(INFO) << "[BioHeadRobot] Serial port opened and ESP32 woken up."; - } - - // 解析眉毛配置 - if (config_.has_eyebrow()) { - const auto& eyebrow = config_.eyebrow(); - std::string serial_str = eyebrow.serial(); - std::string offset_str = eyebrow.offest(); - std::string min_angles_str = eyebrow.jlmtlow(); - std::string max_angles_str = eyebrow.jlmtup(); - - // 解析偏移角 - std::vector offsets; - std::istringstream oss(offset_str); - double val; - while (oss >> val) offsets.push_back(val); - - // 解析角度限制 - std::vector min_angles; - if (!min_angles_str.empty()) { - std::istringstream min_ss(min_angles_str); - while (min_ss >> val) min_angles.push_back(val); - } - - std::vector max_angles; - if (!max_angles_str.empty()) { - std::istringstream max_ss(max_angles_str); - while (max_ss >> val) max_angles.push_back(val); - } - - // 解析 channel,如 64:0~3 - size_t colon = serial_str.find(':'); - if (colon != std::string::npos) { - uint8_t addr = std::stoi(serial_str.substr(0, colon)); - std::string range = serial_str.substr(colon + 1); - size_t tilde = range.find('~'); - if (tilde != std::string::npos) { - int ch_start = std::stoi(range.substr(0, tilde)); - int ch_end = std::stoi(range.substr(tilde + 1)); - int channel_count = ch_end - ch_start + 1; - - // 添加舵机通道 - for (int ch = ch_start; ch <= ch_end; ++ch) { - channels_.push_back({addr, static_cast(ch)}); - } - - // 添加角度限制 - if (min_angles.size() == 1) { - min_angles_.insert(min_angles_.end(), channel_count, min_angles[0]); - } else if (min_angles.size() >= channel_count) { - min_angles_.insert(min_angles_.end(), min_angles.begin(), min_angles.begin() + channel_count); - } else { - min_angles_.insert(min_angles_.end(), channel_count, 0.0); - } - - if (max_angles.size() == 1) { - max_angles_.insert(max_angles_.end(), channel_count, max_angles[0]); - } else if (max_angles.size() >= channel_count) { - max_angles_.insert(max_angles_.end(), max_angles.begin(), max_angles.begin() + channel_count); - } else { - max_angles_.insert(max_angles_.end(), channel_count, 180.0); - } - } - } - } - - // 解析眼睛配置 - if (config_.has_eye()) { - const auto& eye = config_.eye(); - std::string serial_str = eye.serial(); - std::string offset_str = eye.offest(); - std::string min_angles_str = eye.jlmtlow(); - std::string max_angles_str = eye.jlmtup(); - - // 解析偏移角 - std::vector offsets; - std::istringstream oss(offset_str); - double val; - while (oss >> val) offsets.push_back(val); - - // 解析角度限制 - std::vector min_angles; - if (!min_angles_str.empty()) { - std::istringstream min_ss(min_angles_str); - while (min_ss >> val) min_angles.push_back(val); - } - - std::vector max_angles; - if (!max_angles_str.empty()) { - std::istringstream max_ss(max_angles_str); - while (max_ss >> val) max_angles.push_back(val); - } - - // 解析 channel,如 64:4~9 - size_t colon = serial_str.find(':'); - if (colon != std::string::npos) { - uint8_t addr = std::stoi(serial_str.substr(0, colon)); - std::string range = serial_str.substr(colon + 1); - size_t tilde = range.find('~'); - if (tilde != std::string::npos) { - int ch_start = std::stoi(range.substr(0, tilde)); - int ch_end = std::stoi(range.substr(tilde + 1)); - int channel_count = ch_end - ch_start + 1; - - // 添加舵机通道 - for (int ch = ch_start; ch <= ch_end; ++ch) { - channels_.push_back({addr, static_cast(ch)}); - } - - // 添加角度限制 - if (min_angles.size() == 1) { - min_angles_.insert(min_angles_.end(), channel_count, min_angles[0]); - } else if (min_angles.size() >= channel_count) { - min_angles_.insert(min_angles_.end(), min_angles.begin(), min_angles.begin() + channel_count); - } else { - min_angles_.insert(min_angles_.end(), channel_count, 0.0); - } - - if (max_angles.size() == 1) { - max_angles_.insert(max_angles_.end(), channel_count, max_angles[0]); - } else if (max_angles.size() >= channel_count) { - max_angles_.insert(max_angles_.end(), max_angles.begin(), max_angles.begin() + channel_count); - } else { - max_angles_.insert(max_angles_.end(), channel_count, 180.0); - } - } - } - } - - // 解析嘴巴配置 - if (config_.has_mouth()) { - const auto& mouth = config_.mouth(); - std::string serial_str = mouth.serial(); - std::string offset_str = mouth.offest(); - std::string min_angles_str = mouth.jlmtlow(); - std::string max_angles_str = mouth.jlmtup(); - - // 解析偏移角 - std::vector offsets; - std::istringstream oss(offset_str); - double val; - while (oss >> val) offsets.push_back(val); - - // 解析角度限制 - std::vector min_angles; - if (!min_angles_str.empty()) { - std::istringstream min_ss(min_angles_str); - while (min_ss >> val) min_angles.push_back(val); - } - - std::vector max_angles; - if (!max_angles_str.empty()) { - std::istringstream max_ss(max_angles_str); - while (max_ss >> val) max_angles.push_back(val); - } - - // 解析 channel,如 65:0~9 - size_t colon = serial_str.find(':'); - if (colon != std::string::npos) { - uint8_t addr = std::stoi(serial_str.substr(0, colon)); - std::string range = serial_str.substr(colon + 1); - size_t tilde = range.find('~'); - if (tilde != std::string::npos) { - int ch_start = std::stoi(range.substr(0, tilde)); - int ch_end = std::stoi(range.substr(tilde + 1)); - int channel_count = ch_end - ch_start + 1; - - // 添加舵机通道 - for (int ch = ch_start; ch <= ch_end; ++ch) { - channels_.push_back({addr, static_cast(ch)}); - } - - // 添加角度限制 - if (min_angles.size() == 1) { - min_angles_.insert(min_angles_.end(), channel_count, min_angles[0]); - } else if (min_angles.size() >= channel_count) { - min_angles_.insert(min_angles_.end(), min_angles.begin(), min_angles.begin() + channel_count); - } else { - min_angles_.insert(min_angles_.end(), channel_count, 0.0); - } - - if (max_angles.size() == 1) { - max_angles_.insert(max_angles_.end(), channel_count, max_angles[0]); - } else if (max_angles.size() >= channel_count) { - max_angles_.insert(max_angles_.end(), max_angles.begin(), max_angles.begin() + channel_count); - } else { - max_angles_.insert(max_angles_.end(), channel_count, 180.0); - } - } - } - } - - // 确保所有向量大小一致 - if (min_angles_.size() != channels_.size()) { - min_angles_.resize(channels_.size(), 0.0); - } - - if (max_angles_.size() != channels_.size()) { - max_angles_.resize(channels_.size(), 180.0); - } - - current_joints_.resize(channels_.size(), 90.0); - last_joints_ = current_joints_; -} void BioHeadRobot::parseXmlConfig(const XmlNode &node) { diff --git a/cmvr-es/devices/canbus/abstract_canbus.h b/cmvr-es/devices/canbus/abstract_canbus.h index 6fce7f5e..ca0399d0 100644 --- a/cmvr-es/devices/canbus/abstract_canbus.h +++ b/cmvr-es/devices/canbus/abstract_canbus.h @@ -54,7 +54,6 @@ namespace cmvr::device { class AbstractCanbus : public AbstractDevice { public: - AbstractCanbus() = default; AbstractCanbus(const XmlNode &cfg) : AbstractDevice(cfg) {} ~AbstractCanbus() {} /** diff --git a/cmvr-es/devices/canbus/can_client/socket/socket_can_client_raw.cc b/cmvr-es/devices/canbus/can_client/socket/socket_can_client_raw.cc index 28779a06..aef0af2c 100644 --- a/cmvr-es/devices/canbus/can_client/socket/socket_can_client_raw.cc +++ b/cmvr-es/devices/canbus/can_client/socket/socket_can_client_raw.cc @@ -35,17 +35,6 @@ namespace cmvr { // LOG(ERROR) << "[SocketCanClientRaw] ([SocketCanClientRaw]): Failed to parse XML: " << e.what(); } - SocketCanClientRaw::SocketCanClientRaw(const cmvr::config::CanDeviceConfig& config):config_(config) - { - auto channel_id = config_.channelid(); - port_ = static_cast(channel_id); - interface_ = CANCardParameter::NATIVE; - - enable_can_err_check_ = false; - - - } - void SocketCanClientRaw::init() { // if (!parameter.has_channel_id()) { diff --git a/cmvr-es/devices/canbus/can_client/socket/socket_can_client_raw.h b/cmvr-es/devices/canbus/can_client/socket/socket_can_client_raw.h index 04d57c86..3de5b42c 100644 --- a/cmvr-es/devices/canbus/can_client/socket/socket_can_client_raw.h +++ b/cmvr-es/devices/canbus/can_client/socket/socket_can_client_raw.h @@ -26,7 +26,6 @@ #include "gflags/gflags.h" #include "../../abstract_canbus.h" #include "canbus/common/canbus_consts.h" -#include "cmvr/config/robot_config/robot_config.pb.h" namespace cmvr { namespace device { @@ -37,7 +36,6 @@ namespace cmvr { class SocketCanClientRaw final : public AbstractCanbus { public: explicit SocketCanClientRaw(const XmlNode &cfg); - SocketCanClientRaw(const cmvr::config::CanDeviceConfig& config); ~SocketCanClientRaw(); void init() override; void start() override; @@ -75,8 +73,6 @@ namespace cmvr { // bool enable_can_err_check_{false}; - - cmvr::config::CanDeviceConfig config_; }; } } diff --git a/cmvr-es/devices/motor/CMakeLists.txt b/cmvr-es/devices/motor/CMakeLists.txt index 3b7669c9..532924d2 100644 --- a/cmvr-es/devices/motor/CMakeLists.txt +++ b/cmvr-es/devices/motor/CMakeLists.txt @@ -1,5 +1,5 @@ add_subdirectory(ti5_motor) -add_subdirectory(eyou_motor) + # -------------------------------------------------------- # Unit test # -------------------------------------------------------- diff --git a/cmvr-es/devices/motor/abstract_motor.h b/cmvr-es/devices/motor/abstract_motor.h index bb89a415..710f88d2 100644 --- a/cmvr-es/devices/motor/abstract_motor.h +++ b/cmvr-es/devices/motor/abstract_motor.h @@ -41,7 +41,6 @@ namespace cmvr::device{ } JointStatus; public: - AbstractMotor() = default; explicit AbstractMotor(const XmlNode &config): AbstractDevice(config) {} AbstractMotor(const XmlNode &config,uint8_t node_id): AbstractDevice(config) ,node_id_(node_id) {} ~AbstractMotor() override = default; diff --git a/cmvr-es/devices/motor/eyou_motor/CMakeLists.txt b/cmvr-es/devices/motor/eyou_motor/CMakeLists.txt deleted file mode 100644 index 9f00953a..00000000 --- a/cmvr-es/devices/motor/eyou_motor/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -add_library(eyoumotor SHARED - ${CMAKE_CURRENT_SOURCE_DIR}/ethercat/protocol/ec_xml_parser.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ethercat/protocol/ec_master.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ethercat/eyou_motor_ethercat_protocol.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/eyou_motor.cpp - -) - - -target_include_directories(eyoumotor PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - -add_library(cmvr_es::device::eyoumotor ALIAS eyoumotor) - -target_link_libraries(eyoumotor - PRIVATE - -lsoem - protobuf - glog -) - -install(TARGETS eyoumotor LIBRARY DESTINATION lib) \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.cpp b/cmvr-es/devices/motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.cpp deleted file mode 100644 index 26636505..00000000 --- a/cmvr-es/devices/motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.cpp +++ /dev/null @@ -1,747 +0,0 @@ -// -// Created by cmvr on 2026/3/30. -// - -#include "eyou_motor_ethercat_protocol.h" -#include "canbus/canopen/register.h" -#include "motor/ti5_motor/canopen/protocol/ti5_motor_tpdo1.h" -#include "motor/ti5_motor/canopen/protocol/ti5_motor_tpdo2.h" -#include "motor/ti5_motor/canopen/protocol/ti5_motor_sdo_response.h" -#include "canbus/canopen/nmt_response_protocol.h" - -#include -#include -#include - -using namespace cmvr::device; - -// 辅助函数:获取操作模式字符串 -static const char* getOpModeString(int8_t mode) { - switch (mode) { - case 1: return "Profile Position (PP)"; - case 2: return "Velocity (VL)"; - case 3: return "Profile Velocity (PV)"; - case 4: return "Profile Torque (PT)"; - case 6: return "Homing (HM)"; - case 8: return "Cyclic Sync Position (CSP)"; - case 9: return "Cyclic Sync Velocity (CSV)"; - case 10: return "Cyclic Sync Torque (CST)"; - default: return "Unknown"; - } -} - -// 构造函数 -EYouEthercatProtocol::EYouEthercatProtocol(std::string ifName, const std::string& xmlPath) - : interface_name_(ifName), - xml_config_path_(xmlPath), - initialized_(false), - slave_count_(0), - use_fixed_pdo_(xmlPath.empty()) { - - comm_proto = CommProto::ETHERCAT; - - try { - // 初始化message_manager - message_manager_ = std::make_shared>(); - - // 创建 EcMaster 实例 - master_ = std::make_unique(); - - // 初始化主站 - int cycleTimeUs = 1000; // 1ms周期 - int slaveCount = master_->init(interface_name_.c_str(), cycleTimeUs); - - if (slaveCount < 0) { - throw std::runtime_error("[EYouEthercatProtocol] master init failed"); - } - - slave_count_ = slaveCount; - - EcSlaveConfig config = createFixedSlaveConfig(1); - std::vector rxPdos = config.availableRxPdos; - std::vector txPdos = config.availableTxPdos; - - if (rxPdos.empty() || txPdos.empty()) { - LOG(ERROR) << "[EYouEthercatProtocol] No available PDOs found\n"; - throw std::runtime_error("[EYouEthercatProtocol] No available PDOs found "); - } - const EcPdo* rxPdo = &config.modules[0].rxPdos[0]; - const EcPdo* txPdo = &config.modules[0].txPdos[0]; - for (int slaveId = 1; slaveId <= slave_count_; slaveId++) { - if (master_->configurePdo(slaveId, rxPdo, txPdo) != 0) { - throw std::runtime_error("[EYouEthercatProtocol] PDO configuration failed for slave " + std::to_string(slaveId)); - } - } - master_->setupMappingAndDc(); - - // 等待配置生效 - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - // 进入OP状态 - if (master_->gotoOperational() != 0) { - throw std::runtime_error("[EYouEthercatProtocol] OPERATIONAL state failed"); - } - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - if (!master_->isOperational()) { - LOG(WARNING) << "[EYouEthercatProtocol] Master reports not operational"; - } else { - LOG(INFO) << "[EYouEthercatProtocol] EtherCAT in OPERATIONAL state"; - } - - // 初始化从站状态 - for (int i = 1; i <= slave_count_; i++) { - node_states_[i] = NodeState(); - uint16_t status = master_->getStatusWord(i); - LOG(INFO) << "[EYouEthercatProtocol] Slave " << i << " initial status word: 0x" << std::hex << status << std::dec; - } - - initialized_ = true; - LOG(INFO) << "[EYouEthercatProtocol] Initialized successfully"; - - // 添加NMT响应协议 - for (int i = 1; i <= slave_count_; i++) { - message_manager_->AddRecvProtocolData, false>(i); - } - - } catch (const std::exception& e) { - LOG(ERROR) << "[EYouEthercatProtocol] Constructor failed: " << e.what(); - throw std::runtime_error(e.what()); - } -} - -// 析构函数 -EYouEthercatProtocol::~EYouEthercatProtocol() { - if (master_) { - // 失能所有电机 - for (auto& node : node_states_) { - if (node.second.enabled) { - master_->disable(node.first); - LOG(INFO) << "[EYouEthercatProtocol] Disabled motor " << node.first; - } - } - - master_->shutdown(); - LOG(INFO) << "[EYouEthercatProtocol] Master shutdown successfully"; - } -} - -// 执行电机使能序列 -bool EYouEthercatProtocol::performEnableSequence(uint8_t node_id) { - if (node_id > slave_count_) { - LOG(ERROR) << "[EYouEthercatProtocol] Node " << node_id << " out of range"; - return false; - } - // 读取状态字 - uint16_t status = master_->getStatusWord(node_id); - LOG(INFO) << "[EYouEthercatProtocol] Node " << node_id << " status: 0x" << std::hex << status << std::dec; - - // 如果处于故障状态,先复位 - if (status & 0x0008) { - LOG(INFO) << "[EYouEthercatProtocol] Fault detected, resetting..."; - master_->faultReset(node_id); - std::this_thread::sleep_for(std::chrono::milliseconds(200)); - status = master_->getStatusWord(node_id); - } - - // 如果处于 Switch On Disabled 状态,执行使能序列 - if (status & 0x0040) { - LOG(INFO) << "[EYouEthercatProtocol] Node " << (int)node_id << " in Switch On Disabled, enabling..."; - - // 获取当前位置并同步目标位置(防止抖动) - int32_t actualPos = master_->getActualPosition(node_id); - master_->setTargetPosition(node_id, actualPos); - master_->setModeOfOperation(node_id,EcMaster::EcOperationMode_ProfilePosition); - // Shutdown - master_->setControlWord(node_id, 0x06); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - // Switch On - master_->setControlWord(node_id, 0x07); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - // Enable Operation - master_->setControlWord(node_id, 0x0F); - std::this_thread::sleep_for(std::chrono::milliseconds(200)); - - status = master_->getStatusWord(node_id); - } - - // 验证使能成功 - bool enabled = (status & 0x0004) != 0; - if (enabled) { - LOG(INFO) << "[EYouEthercatProtocol] Node " << node_id << " enabled successfully"; - } else { - LOG(ERROR) << "[EYouEthercatProtocol] Node " << node_id << " enable failed, status: 0x" - << std::hex << status << std::dec; - } - - return enabled; -} - -// 操作模式转换 (msgs::RunMode -> EcOperationMode) -int8_t EYouEthercatProtocol::runModeToEcMode(msgs::RunMode mode) { - switch (mode) { - case msgs::RUN_MODE_PROFILE_POSITION: - return EcMaster::EcOperationMode_ProfilePosition; // 1 - - case msgs::RUN_MODE_PROFILE_VELOCITY: - return EcMaster::EcOperationMode_ProfileVelocity; // 3 - - case msgs::RUN_MODE_TORQUE: // 需要确认是否有这个枚举 - return EcMaster::EcOperationMode_ProfileTorque; // 4 - - case msgs::RUN_MODE_INTERPOLATED_POSITION: - return EcMaster::EcOperationMode_InterpolatedPosition; // 7 - - case msgs::RUN_MODE_CYCLIC_SYNC_POSITION: - return EcMaster::EcOperationMode_CyclicSyncPosition; // 8 - - case msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY: - return EcMaster::EcOperationMode_CyclicSyncVelocity; // 9 - - case msgs::RUN_MODE_CYCLIC_SYNC_CURRENT: // 名称统一 - return EcMaster::EcOperationMode_CyclicSyncTorque; // 10 - - default: - return 0; - } -} - -// 操作模式转换 (EcOperationMode -> msgs::RunMode) -cmvr::msgs::RunMode EYouEthercatProtocol::ecModeToRunMode(int8_t mode) { - switch (mode) { - case EcMaster::EcOperationMode_ProfilePosition: - return msgs::RUN_MODE_PROFILE_POSITION; - - case EcMaster::EcOperationMode_ProfileVelocity: - return msgs::RUN_MODE_PROFILE_VELOCITY; - - case EcMaster::EcOperationMode_ProfileTorque: - return msgs::RUN_MODE_TORQUE; // 需要确认枚举存在 - - case EcMaster::EcOperationMode_InterpolatedPosition: - return msgs::RUN_MODE_INTERPOLATED_POSITION; - - case EcMaster::EcOperationMode_CyclicSyncPosition: - return msgs::RUN_MODE_CYCLIC_SYNC_POSITION; - - case EcMaster::EcOperationMode_CyclicSyncVelocity: - return msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY; - - case EcMaster::EcOperationMode_CyclicSyncTorque: - return msgs::RUN_MODE_CYCLIC_SYNC_CURRENT; - - default: - return msgs::RUN_MODE_UNSPECIFIED; - } -} - -// 初始化节点 -bool EYouEthercatProtocol::initNode(uint8_t node_id) { - try { - if (node_id > slave_count_) { - LOG(ERROR) << "[EYouEthercatProtocol] Node " << node_id << " out of range (max: " << slave_count_ << ")"; - return false; - } - - // 执行使能序列 - if (!performEnableSequence(node_id)) { - LOG(ERROR) << "[EYouEthercatProtocol] Failed to enable node " << node_id; - return false; - } - - node_states_[node_id].initialized = true; - node_states_[node_id].enabled = true; - node_states_[node_id].current_mode = msgs::RUN_MODE_PROFILE_POSITION; - - LOG(INFO) << "[EYouEthercatProtocol] Node " << node_id << " initialized successfully"; - return true; - - } catch (const std::exception& e) { - LOG(ERROR) << "[EYouEthercatProtocol] Failed to init node " << node_id << ": " << e.what(); - return false; - } -} - -// 设置运行模式 -void EYouEthercatProtocol::setMode(uint8_t node_id, msgs::RunMode mode) { - - - int8_t ec_mode = runModeToEcMode(mode); - master_->setModeOfOperation(node_id, ec_mode); - - // 验证模式设置 - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - int8_t actual_mode = master_->getModeOfOperationDisplay(node_id); - - if (actual_mode == ec_mode) { - node_states_[node_id].current_mode = mode; - LOG(INFO) << "[EYouEthercatProtocol] Node " << (int)node_id << " mode set to " - << getOpModeString(ec_mode); - } else { - LOG(WARNING) << "[EYouEthercatProtocol] Node " << node_id << " mode set failed, " - << "expected " << getOpModeString(ec_mode) - << ", got " << getOpModeString(actual_mode); - } -} - -// 设置目标位置和速度 -void EYouEthercatProtocol::setTarget(uint8_t node_id, double angle_rad, double vel) { - auto pos_cmd = angleToPos(angle_rad); - auto speed = static_cast(std::abs(velToSpeed(vel))); - - msgs::RunMode current_mode = getMode(node_id); - - switch (current_mode) { - case msgs::RUN_MODE_PROFILE_POSITION: { - // 先设置轮廓速度 - master_->setProfileVelocity(node_id, speed); - // 设置目标位置 - master_->setTargetPosition(node_id, pos_cmd); - node_states_[node_id].target_position = pos_cmd; - - // 构建控制字:使能状态 + 立即更新标志 - uint16_t ctlword = 0x0F | 0x20; // 0x0F使能, 0x20立即更新 - - // 触发位置更新 - master_->setControlWord(node_id, ctlword); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - - // 启动新位置命令 - master_->setControlWord(node_id, ctlword | 0x10); - break; - } - case msgs::RUN_MODE_CYCLIC_SYNC_POSITION: - master_->setTargetPosition(node_id, pos_cmd); - node_states_[node_id].target_position = pos_cmd; - break; - default: - LOG(WARNING) << "[EYouEthercatProtocol] setTarget(pos,vel) called in unsupported mode for node " << node_id; - break; - } -} - -// 设置目标速度 -void EYouEthercatProtocol::setTarget(uint8_t node_id, double vel) { - auto speed = static_cast(velToSpeed(vel)); - - - - msgs::RunMode current_mode = getMode(node_id); - - switch (current_mode) { - case msgs::RUN_MODE_VELOCITY: - case msgs::RUN_MODE_PROFILE_VELOCITY: - case msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY: - master_->setTargetVelocity(node_id, speed); - break; - default: - LOG(WARNING) << "[EYouEthercatProtocol] setTarget(vel) called in unsupported mode for node " << node_id; - break; - } -} - -// 设置目标位置 -void EYouEthercatProtocol::setQ(uint8_t node_id, double angle_rad) { - auto pos_cmd = angleToPos(angle_rad); - - - - msgs::RunMode current_mode = getMode(node_id); - - switch (current_mode) { - case msgs::RUN_MODE_CYCLIC_SYNC_POSITION: - { - master_->setTargetPosition(node_id, pos_cmd); - node_states_[node_id].target_position = pos_cmd; - break; - } - case msgs::RUN_MODE_PROFILE_POSITION: - { - // 1. 设置目标位置 - master_->setTargetPosition(node_id, pos_cmd); - node_states_[node_id].target_position = pos_cmd; - - // 2. 构建控制字:使能状态 + 立即更新标志 - uint16_t ctlword = 0x0F | 0x20; // 0x0F使能, 0x20立即更新 - - // 3. 触发位置更新 - master_->setControlWord(node_id, ctlword); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - - // 4. 启动新位置命令 - master_->setControlWord(node_id, ctlword | 0x10); - break; - } - default: - { - LOG(WARNING) << "[EYouEthercatProtocol] setQ called in unsupported mode for node " << node_id; - break; - } - } -} - -// 设置位置限制 -void EYouEthercatProtocol::setLimitQ(uint8_t node_id, double ub, double lb) { - - - int32_t lb_val = angleToPos(lb); - int32_t ub_val = angleToPos(ub); - - // 开启软件位置限制 (0x4C494D54 = "LIMT") - uint32_t limitEnable = 0x4C494D54; - if (master_->writeSdo(node_id, 0x2003, 0x00, &limitEnable, EcDataType::Uint32, 100) != 0) { - LOG(ERROR) << "[EYouEthercatProtocol] Failed to enable software position limit for node " << node_id; - } - - // 设置软件位置限制 - if (master_->writeSdo(node_id, 0x607D, 0x01, &lb_val, EcDataType::Int32, 100) != 0) { - LOG(ERROR) << "[EYouEthercatProtocol] Failed to set lower position limit for node " << node_id; - } - - if (master_->writeSdo(node_id, 0x607D, 0x02, &ub_val, EcDataType::Int32, 100) != 0) { - LOG(ERROR) << "[EYouEthercatProtocol] Failed to set upper position limit for node " << node_id; - } - - LOG(INFO) << "[EYouEthercatProtocol] Position limits set for node " << node_id - << ": [" << lb << ", " << ub << "] rad"; -} - -// 设置速度限制 -void EYouEthercatProtocol::setLimitQd(uint8_t node_id, double qd) { - - - auto speed = static_cast(std::abs(velToSpeed(qd))); - - // 设置最大速度限制 - if (master_->writeSdo(node_id, 0x607F, 0x00, &speed, EcDataType::Uint32, 100) != 0) { - LOG(ERROR) << "[EYouEthercatProtocol] Failed to set max speed limit for node " << node_id; - } - - if (master_->writeSdo(node_id, 0x6081, 0x00, &speed, EcDataType::Uint32, 100) != 0) { - LOG(ERROR) << "[EYouEthercatProtocol] Failed to set profile speed for node " << node_id; - } - - LOG(INFO) << "[EYouEthercatProtocol] Speed limit set for node " << node_id << ": " << qd << " rad/s"; -} - -// 设置加速度限制 -void EYouEthercatProtocol::setLimitQdd(uint8_t node_id, double u_qdd, double l_qdd) { - - - // u_qdd: rad/s² (最大加速度) - // l_qdd: rad/s² (最大减速度) - // 转换为电机加速度(脉冲/秒²) - // 圈数/秒² = 弧度/秒² / 2π - // 脉冲/秒² = 圈数/秒² * GearRatio * PULSE_PER_REV - auto accel = static_cast((std::abs(u_qdd) / (2.0 * M_PI)) * GearRatio * PULSE_PER_REV); - auto decel = static_cast((std::abs(l_qdd) / (2.0 * M_PI)) * GearRatio * PULSE_PER_REV); - - // 通过SDO写入加速度和减速度限制 - if (master_->writeSdo(node_id, 0x6083, 0x00, &accel, EcDataType::Uint32, 100) != 0) { - LOG(ERROR) << "[EYouEthercatProtocol] Failed to set acceleration limit for node " << node_id; - } - - if (master_->writeSdo(node_id, 0x6084, 0x00, &decel, EcDataType::Uint32, 100) != 0) { - LOG(ERROR) << "[EYouEthercatProtocol] Failed to set deceleration limit for node " << node_id; - } - - LOG(INFO) << "[EYouEthercatProtocol] Acceleration limits set for node " << node_id - << ": accel=" << u_qdd << ", decel=" << l_qdd << " rad/s²"; -} - -// 零位校准 -bool EYouEthercatProtocol::calibrateZeroQ(uint8_t node_id) { - - - try { - int32_t pos; - int32_t offset = 0; - int32_t rev = 0; - - // 读取当前位置 - if (master_->readSdo(node_id, 0x6064, 0x00, &pos, EcDataType::Int32, 20000) != 0) { - LOG(ERROR) << "[EYouEthercatProtocol] read actual position failed"; - return false; - } - LOG(INFO) << "[EYouEthercatProtocol] Actual position: " << pos; - - // 读取home offset - if (master_->readSdo(node_id, 0x607C, 0x00, &offset, EcDataType::Int32, 20000) != 0) { - LOG(ERROR) << "[EYouEthercatProtocol] read home offset failed"; - return false; - } - LOG(INFO) << "[EYouEthercatProtocol] Home offset before: " << offset; - - // 先将home offset设为0 - offset = 0; - if (master_->writeSdo(node_id, 0x607C, 0x00, &offset, EcDataType::Int32, 20000) != 0) { - LOG(ERROR) << "[EYouEthercatProtocol] write home offset 0 failed"; - return false; - } - LOG(INFO) << "[EYouEthercatProtocol] Home offset set to 0"; - - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - - // 读取实际位置 - if (master_->readSdo(node_id, 0x6064, 0x00, &pos, EcDataType::Int32, 20000) != 0) { - LOG(ERROR) << "[EYouEthercatProtocol] read actual position after offset reset failed"; - return false; - } - LOG(INFO) << "[EYouEthercatProtocol] Actual position after offset reset: " << pos; - - // 获取轴转数,超圈处理 - if (master_->readSdo(node_id, 0x6091, 0x02, &rev, EcDataType::Int32, 20000) != 0) { - LOG(ERROR) << "[EYouEthercatProtocol] read gear ratio shaft revolution failed"; - return false; - } - LOG(INFO) << "[EYouEthercatProtocol] Gear ratio shaft revolution: " << rev; - - // 处理超出编码器范围的情况 - long long r = rev; - int temp = static_cast(r / 2); - if (pos > temp || pos < -temp) { - pos = pos % static_cast(r); - if (pos > temp || pos < -temp) { - if (pos > temp) { - pos = pos - static_cast(r); - } else if (pos < -temp) { - pos = pos + static_cast(r); - } - } - LOG(WARNING) << "[EYouEthercatProtocol] Position out of encoder range, adjusted to " << pos; - } - - // 写home offset偏移 - offset = -pos; - if (master_->writeSdo(node_id, 0x607C, 0x00, &offset, EcDataType::Int32, 20000) != 0) { - LOG(ERROR) << "[EYouEthercatProtocol] write home offset failed"; - return false; - } - LOG(INFO) << "[EYouEthercatProtocol] Home offset set to: " << offset; - - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - - // 读取实际位置 - if (master_->readSdo(node_id, 0x6064, 0x00, &pos, EcDataType::Int32, 20000) != 0) { - LOG(ERROR) << "[EYouEthercatProtocol] read actual position after calibration failed"; - return false; - } - LOG(INFO) << "[EYouEthercatProtocol] Actual position after calibration: " << pos; - - // 保存参数 - uint32_t saveValue = 0x65766173; // "evas" - if (master_->writeSdo(node_id, 0x1010, 0x01, &saveValue, EcDataType::Uint32, 20000) != 0) { - LOG(ERROR) << "[EYouEthercatProtocol] save parameters failed"; - return false; - } - LOG(INFO) << "[EYouEthercatProtocol] Parameters saved"; - - LOG(INFO) << "[EYouEthercatProtocol] Motor " << node_id << " calibrated successfully"; - return true; - - } catch (const std::exception& e) { - LOG(ERROR) << "[EYouEthercatProtocol] Calibration failed for node " << node_id << ": " << e.what(); - return false; - } -} - -// 刹车(快速停机) -void EYouEthercatProtocol::brake(uint8_t node_id) { - - - // 发送快速停机命令 - uint16_t ctlword = 0x0002; // Quick Stop - master_->setControlWord(node_id, ctlword); - - LOG(INFO) << "[EYouEthercatProtocol] Brake (quick stop) applied to motor " << node_id; -} - -// 检查是否到达目标位置 -bool EYouEthercatProtocol::reachedTargetQ(uint8_t node_id) { - uint16_t status_word = getStatusWord(node_id); - // 检查状态字的bit 10(目标到达位) - return (status_word & 0x0400) != 0; -} - -// 获取当前位置 -double EYouEthercatProtocol::getQ(uint8_t node_id) { - - int32_t position = master_->getActualPosition(node_id); - return posToAngle(position); -} - -// 获取当前速度 -double EYouEthercatProtocol::getQd(uint8_t node_id) { - - int32_t velocity = master_->getActualVelocity(node_id); - return speedToVel(velocity); -} - -// 设置速度 -void EYouEthercatProtocol::setQd(uint8_t node_id, double qd) { - auto speed = static_cast(std::abs(velToSpeed(qd))); - - - - msgs::RunMode current_mode = getMode(node_id); - - switch (current_mode) { - case msgs::RUN_MODE_CYCLIC_SYNC_POSITION: - case msgs::RUN_MODE_PROFILE_POSITION: - master_->setProfileVelocity(node_id, speed); - last_Qd_[node_id] = speed; - break; - case msgs::RUN_MODE_PROFILE_VELOCITY: - case msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY: - master_->setTargetVelocity(node_id, static_cast(speed)); - break; - default: - LOG(WARNING) << "[EYouEthercatProtocol] setQd called in unsupported mode for node " << node_id; - break; - } -} - -// 设置加速度 -void EYouEthercatProtocol::setQdd(uint8_t node_id, double qdd) { - // qdd: rad/s² (加速度) - // 转换为电机加速度(脉冲/秒²) - uint32_t accel = static_cast((std::abs(qdd) / (2.0 * M_PI)) * GearRatio * PULSE_PER_REV); - - if (last_Qdd_[node_id] != accel) { - - master_->setProfileAcceleration(node_id, accel); - master_->setProfileDeceleration(node_id, accel); - last_Qdd_[node_id] = accel; - } -} - -// 力矩关闭 -void EYouEthercatProtocol::torqueOff(uint8_t node_id) { - - auto ret = master_->disable(node_id); - node_states_[node_id].enabled = false; - LOG(INFO) << "[EYouEthercatProtocol] Torque off for motor " << node_id << ", ret = " << ret; -} - -// 获取运行模式 -cmvr::msgs::RunMode EYouEthercatProtocol::getMode(uint8_t node_id) { - - int8_t mode = master_->getModeOfOperationDisplay(node_id); - return ecModeToRunMode(mode); -} - -// 配置轮廓参数 -void EYouEthercatProtocol::configProfile(uint8_t node_id, uint32_t speed, uint32_t accel, uint32_t decel) { - - master_->setProfileVelocity(node_id, speed); - master_->setProfileAcceleration(node_id, accel); - master_->setProfileDeceleration(node_id, decel); - LOG(INFO) << "[EYouEthercatProtocol] Profile configured for node " << node_id - << ": speed=" << speed << ", accel=" << accel << ", decel=" << decel; -} - -// 获取力矩 -double EYouEthercatProtocol::getTorque(uint8_t node_id) { - - int16_t torque = master_->getActualTorque(node_id); - // 返回实际力矩值(取决于具体驱动器,这里假设是额定力矩的倍数) - return torque / 1000.0; -} - -// 获取状态字 -uint16_t EYouEthercatProtocol::getStatusWord(uint8_t node_id) { - - uint16_t status_word = master_->getStatusWord(node_id); - node_states_[node_id].status_word = status_word; - return status_word; -} - -// 检查电机是否使能 -bool EYouEthercatProtocol::isEnabled(uint8_t node_id) { - uint16_t status_word = getStatusWord(node_id); - // 检查状态字的bit 2(操作使能)和bit 3(故障) - return (status_word & 0x000C) == 0x0008; -} - -// 检查是否有故障 -bool EYouEthercatProtocol::isFault(uint8_t node_id) { - uint16_t status_word = getStatusWord(node_id); - return (status_word & 0x0008) != 0; -} - -// 故障复位 -void EYouEthercatProtocol::faultReset(uint8_t node_id) { - if (isFault(node_id)) { - - if (master_->faultReset(node_id) == 0) { - LOG(INFO) << "[EYouEthercatProtocol] Fault reset for motor " << node_id; - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } else { - LOG(ERROR) << "[EYouEthercatProtocol] Failed to reset fault for motor " << node_id; - } - } -} - -// 等待目标到达 -bool EYouEthercatProtocol::waitForTargetReached(uint8_t node_id, int timeout_ms) { - return waitUntil([this, node_id]() { return reachedTargetQ(node_id); }, timeout_ms); -} - -// 获取RobotDetail数据 -std::unique_ptr EYouEthercatProtocol::GetRobotDetail() { - if (message_manager_) { - auto data_ptr = std::make_unique(); - message_manager_->GetSensorData(data_ptr.get()); - return data_ptr; - } - return std::make_unique(); -} - -// 辅助函数实现(弧度制版本) -double EYouEthercatProtocol::posToAngle(int32_t pos) const { - // 脉冲数 -> 弧度 - // 圈数 = 脉冲数 / (GearRatio * PULSE_PER_REV) - // 弧度 = 圈数 * 2π - return (pos * 2.0 * M_PI) / (GearRatio * PULSE_PER_REV); -} - -int32_t EYouEthercatProtocol::angleToPos(double angle) const { - // 弧度 -> 脉冲数 - // 圈数 = 弧度 / 2π - // 脉冲数 = 圈数 * GearRatio * PULSE_PER_REV - return static_cast((angle / (2.0 * M_PI)) * GearRatio * PULSE_PER_REV); -} - -double EYouEthercatProtocol::velToSpeed(double vel) const { - // vel: rad/s (角速度) - // 输出:电机速度(脉冲/秒) - // 圈数/秒 = vel / 2π - // 脉冲/秒 = 圈数/秒 * GearRatio * PULSE_PER_REV - return (vel / (2.0 * M_PI)) * GearRatio * PULSE_PER_REV; -} - -double EYouEthercatProtocol::speedToVel(int32_t speed) const { - // speed: 电机速度(脉冲/秒) - // 输出:rad/s - // 圈数/秒 = speed / (GearRatio * PULSE_PER_REV) - // 弧度/秒 = 圈数/秒 * 2π - return (speed * 2.0 * M_PI) / (GearRatio * PULSE_PER_REV); -} - -bool EYouEthercatProtocol::waitUntil(std::function condition, int timeout_ms) { - auto start = std::chrono::steady_clock::now(); - while (!condition()) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - if (std::chrono::steady_clock::now() - start > std::chrono::milliseconds(timeout_ms)) { - return false; - } - } - return true; -} - -void EYouEthercatProtocol::updateNodeState(uint8_t node_id) { - - // 更新状态字 - node_states_[node_id].status_word = master_->getStatusWord(node_id); - // 可以根据需要更新其他状态信息 -} \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.h b/cmvr-es/devices/motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.h deleted file mode 100644 index 2b14279c..00000000 --- a/cmvr-es/devices/motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.h +++ /dev/null @@ -1,125 +0,0 @@ -// -// Created by cmvr on 2026/3/30. -// - -#pragma once -#include "cmvr/msgs/motor.pb.h" -#include "motor/motor_protocol_interface.h" -#include "../../../canbus/abstract_canbus.h" -#include "canbus/can_comm/can_receiver.h" -#include "canbus/can_comm/can_sender.h" -#include "canbus/can_comm/message_manager.h" -#include "cmvr/msgs/error_code.pb.h" -#include "cmvr/msgs/robot_detail.pb.h" - -// 替换原有的 eu_ethercat 头文件 -#include "protocol/ec_master.h" -#include "protocol/ec_xml_parser.h" -#include "protocol/ec_pdo_config.h" - -#include -#include -#include -#include -#include -#include -#include - -namespace cmvr { - namespace device { - - // 节点状态结构体 - struct NodeState { - bool enabled = false; - bool initialized = false; - msgs::RunMode current_mode = msgs::RUN_MODE_UNSPECIFIED; - uint16_t status_word = 0; - int32_t position_offset = 0; - int32_t target_position = 0; - }; - - class EYouEthercatProtocol : public MotorProtocolInterface { - public: - explicit EYouEthercatProtocol(std::string ifName, const std::string& xmlPath = ""); - ~EYouEthercatProtocol() override; - - // 基础接口实现 - bool initNode(uint8_t node_id) override; - void setMode(uint8_t node_id, msgs::RunMode mode) override; - void setTarget(uint8_t node_id, double angle_rad, double vel) override; - void setTarget(uint8_t node_id, double vel) override; - void setQ(uint8_t node_id, double angle_rad) override; - void setLimitQ(uint8_t node_id, double ub, double lb) override; - void setLimitQd(uint8_t node_id, double qd) override; - void setLimitQdd(uint8_t node_id, double u_qdd, double l_qdd) override; - bool calibrateZeroQ(uint8_t node_id) override; - void brake(uint8_t node_id) override; - bool reachedTargetQ(uint8_t node_id) override; - - double getQ(uint8_t node_id) override; - double getQd(uint8_t node_id) override; - - void setQd(uint8_t node_id, double qd) override; - void setQdd(uint8_t node_id, double qdd) override; - - void torqueOff(uint8_t node_id) override; - msgs::RunMode getMode(uint8_t node_id) override; - - // 配置接口 - void configProfile(uint8_t node_id, uint32_t speed, uint32_t accel, uint32_t decel); - - // 扩展功能接口 - double getTorque(uint8_t node_id); - uint16_t getStatusWord(uint8_t node_id); - bool isEnabled(uint8_t node_id); - bool isFault(uint8_t node_id); - void faultReset(uint8_t node_id); - bool waitForTargetReached(uint8_t node_id, int timeout_ms); - - // 数据获取接口 - std::unique_ptr GetRobotDetail(); - - int getSlaveCount() const { return slave_count_; } - - private: - // 常量定义 - static constexpr double GearRatio = 101.0; - static constexpr double RADTODEG = 180.0 / M_PI; - static constexpr double PULSE_PER_REV = 65536.0; - - // 辅助函数 - double posToAngle(int32_t pos) const; - int32_t angleToPos(double angle) const; - double velToSpeed(double vel) const; - double speedToVel(int32_t speed) const; - - bool waitUntil(std::function condition, int timeout_ms); - void updateNodeState(uint8_t node_id); - bool performEnableSequence(uint8_t node_id); - - int8_t runModeToEcMode(msgs::RunMode mode); - msgs::RunMode ecModeToRunMode(int8_t mode); - - // 成员变量 - std::unordered_map node_states_; - std::unordered_map last_Qd_; - std::unordered_map last_Qdd_; - - std::shared_ptr> can_sender_; - std::shared_ptr> message_manager_; - - std::string interface_name_; - std::string xml_config_path_; - bool initialized_; - int slave_count_; - - std::unique_ptr master_; - std::unique_ptr parser_; - EcSlaveConfig slave_config_; - bool use_fixed_pdo_; - - std::mutex master_mutex_; - }; - - } // namespace device -} // namespace cmvr \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_master.cpp b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_master.cpp deleted file mode 100644 index afac1e5c..00000000 --- a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_master.cpp +++ /dev/null @@ -1,1143 +0,0 @@ -// ec_master.cpp -#include "ec_master.h" -#include - -static float pgain = 0.01f; -static float igain = 0.00002f; -static int64_t syncoffset = 500000; -static int64_t timeerror = 0; - -static void add_time_ns(ec_timet* ts, int64_t addtime) { - ec_timet addts; - addts.tv_nsec = addtime % 1000000000; - addts.tv_sec = (addtime - addts.tv_nsec) / 1000000000; - osal_timespecadd(ts, &addts, ts); -} - -// DC 同步 PI 计算 -static void ec_sync(int64_t reftime, int64_t cycletime, int64_t *offsettime) { - static int64_t integral = 0; - int64_t delta; - delta = (reftime - syncoffset) % cycletime; - if (delta > (cycletime / 2)) { - delta = delta - cycletime; - } - timeerror = -delta; - integral += timeerror; - *offsettime = (int64_t)((timeerror * pgain) + (integral * igain)); -} - -// ==================== 构造和析构 ==================== - -EcMaster::EcMaster() { - memset(&ctx, 0, sizeof(ctx)); - memset(IOmap, 0, sizeof(IOmap)); - - slaveCount = 0; - expectedWKC = 0; - currentgroup = 0; - inOp = false; - isRunning = false; - mappingdone = 0; - dorun = 0; - cyclicWkcErrorCount = 0; - cycleTimeNs = 1000000; - cyclicThread = nullptr; - checkThread = nullptr; - anyPdoDirty = false; -} - -EcMaster::~EcMaster() { - shutdown(); - // shadowBuffers 的析构函数会自动清理内存 -} - -// ==================== 初始化和状态控制 ==================== - -int EcMaster::init(const char* ifName, int cycleTimeUs) { - cycleTimeNs = cycleTimeUs * 1000; - - if (!ecx_init(&ctx, ifName)) { - printf("Failed to init EtherCAT on %s\n", ifName); - return -EC_FAILED_INIT; - } - - ecx_config_init(&ctx); - slaveCount = ctx.slavecount; - - if (slaveCount <= 0) { - printf("No slave found\n"); - return -EC_FAILED_NOSLAVE; - } - - printf("%d slaves found\n", slaveCount); - - isRunning = true; - mappingdone = 0; - dorun = 0; - inOp = false; - cyclicWkcErrorCount = 0; - anyPdoDirty = false; - - osal_thread_create_rt(&cyclicThread, 128000, reinterpret_cast(cyclicThreadFunc), this); - - return slaveCount; -} - -int EcMaster::setState(uint16_t slaveId, uint8_t targetState) { - if (slaveId > (uint16_t)ctx.slavecount) return -1; - - int oldManual = ctx.manualstatechange; - ctx.manualstatechange = 1; - - ctx.slavelist[slaveId].state = targetState; - ecx_writestate(&ctx, slaveId); - - int ret = ecx_statecheck(&ctx, slaveId, targetState, EC_TIMEOUTSTATE); - ctx.manualstatechange = oldManual; - - if (ret != targetState) { - printf("Slave %d failed to enter state %d (current: %d)\n", slaveId, targetState, ret); - return -1; - } - - return 0; -} - -int EcMaster::gotoOperational() { - while (!mappingdone) { - osal_usleep(100); - } - - dorun = 1; - - printf("Waiting for PDO exchange to stabilize...\n"); - osal_usleep(500000); - - // ========== 逐个处理每个从站 ========== - int allOperational = 1; - for (int i = 1; i <= ctx.slavecount; i++) { - printf("\n--- Processing slave %d ---\n", i); - - // 1. 检查并清除错误状态 - if (ctx.slavelist[i].state == (EC_STATE_SAFE_OP + EC_STATE_ERROR)) { - printf(" Clearing error on slave %d\n", i); - ctx.slavelist[i].state = (EC_STATE_SAFE_OP + EC_STATE_ACK); - ecx_writestate(&ctx, i); - osal_usleep(100000); - - // 刷新状态 - ecx_readstate(&ctx); - printf(" Slave %d state after error clear: 0x%02X\n", i, ctx.slavelist[i].state); - } - - // 2. 请求进入 OP 状态 - printf(" Requesting OPERATIONAL state for slave %d...\n", i); - ctx.slavelist[i].state = EC_STATE_OPERATIONAL; - ecx_writestate(&ctx, i); - - // 3. 等待该从站进入 OP 状态 - printf(" Waiting for slave %d to enter OPERATIONAL...\n", i); - - int retry = 0; - const int maxRetry = 200; // 最多等待2秒 (200 * 10ms) - int currentState = 0; - - while (retry < maxRetry) { - osal_usleep(10000); // 10ms - ecx_readstate(&ctx); - currentState = ctx.slavelist[i].state; - - if (currentState == EC_STATE_OPERATIONAL) { - printf(" ✓ Slave %d entered OPERATIONAL after %d ms\n", i, retry * 10); - break; - } - - // 如果状态异常,提前退出 - if (currentState == EC_STATE_INIT || currentState == EC_STATE_PRE_OP) { - printf(" ✗ Slave %d regressed to state 0x%02X\n", i, currentState); - break; - } - - retry++; - } - - // 4. 检查结果 - if (currentState == EC_STATE_OPERATIONAL) { - printf(" ✓ Slave %d is OPERATIONAL\n", i); - } else { - printf(" ✗ Slave %d failed to enter OPERATIONAL, state: 0x%02X\n", i, currentState); - - // 读取错误码 - uint16_t alStatusCode = 0; - int psize = 2; - ecx_SDOread(&ctx, i, 0x0134, 0, FALSE, &psize, &alStatusCode, EC_TIMEOUTRXM); - printf(" AL Status Code: 0x%04X\n", alStatusCode); - - allOperational = 0; - } - } - - // 5. 检查是否所有从站都进入了 OP - if (!allOperational) { - printf("\n✗ Failed to enter OPERATIONAL\n"); - return -1; - } - - printf("\n✓ All slaves are OPERATIONAL\n"); - - // 6. 启动 check 线程 - printf("Starting check thread for runtime monitoring...\n"); - osal_thread_create(&checkThread, 128000, reinterpret_cast(checkThreadFunc), this); - - osal_usleep(200000); - inOp = true; - printf("EtherCAT OPERATIONAL (all slaves)\n"); - return 0; -} - -void EcMaster::shutdown() { - printf("Shutting down...\n"); - - if (inOp) { - inOp = false; - dorun = 0; - osal_usleep(100000); - } - - for (int i = 1; i <= slaveCount; i++) { - printf(" Resetting slave %d to INIT...\n", i); - ctx.slavelist[i].state = EC_STATE_INIT; - ecx_writestate(&ctx, i); - osal_usleep(50000); - ecx_statecheck(&ctx, i, EC_STATE_INIT, 500000); - } - - isRunning = false; - osal_usleep(100000); - - shadowBuffers.clear(); - - printf("Shutdown complete\n"); -} - -// ==================== 影子缓冲区管理 ==================== - -EcMaster::SlaveShadowBuffer* EcMaster::getShadowBuffer(uint16_t slaveId) { - auto it = shadowBuffers.find(slaveId); - if (it != shadowBuffers.end()) { - return &it->second; - } - return nullptr; -} - -void EcMaster::syncSlaveOutputs(uint16_t slaveId) { - auto* shadow = getShadowBuffer(slaveId); - if (!shadow || !shadow->dirty || !shadow->data) return; - - auto outIt = outputs.find(slaveId); - auto sizeIt = outputSizes.find(slaveId); - if (outIt == outputs.end() || outIt->second == nullptr) return; - if (sizeIt == outputSizes.end()) return; - - size_t copySize = std::min(shadow->size, (size_t)sizeIt->second); - memcpy(outIt->second, shadow->data, copySize); - shadow->dirty = false; -} - -void EcMaster::syncAllOutputs() { - if (!anyPdoDirty.load()) return; - - for (auto& pair : shadowBuffers) { - uint16_t slaveId = pair.first; - SlaveShadowBuffer& shadow = pair.second; - - if (!shadow.dirty || !shadow.data) continue; - - auto outIt = outputs.find(slaveId); - auto sizeIt = outputSizes.find(slaveId); - if (outIt == outputs.end() || outIt->second == nullptr) continue; - if (sizeIt == outputSizes.end()) continue; - - size_t copySize = std::min(shadow.size, (size_t)sizeIt->second); - memcpy(outIt->second, shadow.data, copySize); - shadow.dirty = false; - } - - anyPdoDirty = false; -} - -// ==================== 线程函数 ==================== - -OSAL_THREAD_FUNC_RT EcMaster::cyclicThreadFunc(void* arg) { - static_cast(arg)->runCyclic(); -} - -OSAL_THREAD_FUNC EcMaster::checkThreadFunc(void* arg) { - static_cast(arg)->runCheck(); -} - -void EcMaster::runCyclic() { - ec_timet ts; - int ht; - static int64_t toff = 0; - int cycleCount = 0; - int printInterval = 500; - - printf("[Cyclic Thread] Thread started, waiting for mappingdone...\n"); - - while (!mappingdone && isRunning) { - osal_usleep(100); - } - - if (!isRunning) { - printf("[Cyclic Thread] Exiting - master not running\n"); - return; - } - - printf("[Cyclic Thread] Mapping done, initializing timing...\n"); - - osal_get_monotonic_time(&ts); - ht = (ts.tv_nsec / 1000000) + 1; - ts.tv_nsec = ht * 1000000; - - printf("[Cyclic Thread] Entering main cyclic loop (cycleTime=%lld ns)\n", - (long long)cycleTimeNs); - - int receiveSuccessCount = 0; - int receiveFailCount = 0; - int lastPrintCycle = 0; - - ecx_send_processdata(&ctx); - - while (isRunning) { - add_time_ns(&ts, cycleTimeNs + toff); - osal_monotonic_sleep(&ts); - - cycleCount++; - - if (dorun) { - // ========== 同步所有从站的影子缓冲区到 outputs ========== - if (anyPdoDirty.load()) { - for (auto& pair : shadowBuffers) { - uint16_t slaveId = pair.first; - SlaveShadowBuffer& shadow = pair.second; - - if (!shadow.dirty || !shadow.data) continue; - - auto outIt = outputs.find(slaveId); - auto sizeIt = outputSizes.find(slaveId); - if (outIt == outputs.end() || outIt->second == nullptr) continue; - if (sizeIt == outputSizes.end()) continue; - - size_t copySize = std::min(shadow.size, (size_t)sizeIt->second); - memcpy(outIt->second, shadow.data, copySize); - shadow.dirty = false; - } - anyPdoDirty = false; - } - - ecx_send_processdata(&ctx); - - int wkc = ecx_receive_processdata(&ctx, EC_TIMEOUTRET); - - if (wkc != expectedWKC) { - cyclicWkcErrorCount++; - receiveFailCount++; - if (cyclicWkcErrorCount <= 5 || cyclicWkcErrorCount % 100 == 0) { - printf("[Cyclic] WARNING: WKC mismatch! Expected=%d, Got=%d, ErrorCount=%d\n", - expectedWKC, wkc, cyclicWkcErrorCount.load()); - } - } else { - receiveSuccessCount++; - } - - if (ctx.slavelist[0].hasdc && (wkc > 0)) { - ec_sync(ctx.DCtime, cycleTimeNs, &toff); - } - - ecx_mbxhandler(&ctx, 0, 4); - - // if (cycleCount - lastPrintCycle >= printInterval) { - // lastPrintCycle = cycleCount; - // - // printf("\n========== PDO Data Exchange Status ==========\n"); - // printf("Cycle: %d | Success: %d | Fail: %d | WKC Error: %d\n", - // cycleCount, receiveSuccessCount, receiveFailCount, cyclicWkcErrorCount.load()); - // printf("DC time: %lld | toff: %lld\n", (long long)ctx.DCtime, (long long)toff); - // - // for (int slaveIdx = 1; slaveIdx <= slaveCount; slaveIdx++) { - // ec_slavet* slave = &ctx.slavelist[slaveIdx]; - // - // printf("\n[Slave %d] State: 0x%02X | AL Status: 0x%04X\n", - // slaveIdx, slave->state, slave->ALstatuscode); - // - // if (slave->inputs && slave->Ibytes > 0) { - // printf(" Inputs (%d bytes): ", slave->Ibytes); - // for (int i = 0; i < slave->Ibytes && i < 16; i++) { - // printf("%02X ", ((uint8_t*)slave->inputs)[i]); - // } - // if (slave->Ibytes > 16) printf("..."); - // printf("\n"); - // - // if (slave->Ibytes >= 2) { - // uint16_t statusWord = *(uint16_t*)slave->inputs; - // printf(" -> Status Word: 0x%04X", statusWord); - // if (statusWord == 0xFFFF) { - // printf(" (INVALID - All ones)"); - // } else if (statusWord == 0) { - // printf(" (INVALID - All zeros)"); - // } else { - // printf(" ["); - // if (statusWord & 0x0001) printf("ReadyToSwitchOn "); - // if (statusWord & 0x0002) printf("SwitchedOn "); - // if (statusWord & 0x0004) printf("OperationEnabled "); - // if (statusWord & 0x0008) printf("Fault "); - // if (statusWord & 0x0010) printf("VoltageEnabled "); - // if (statusWord & 0x0020) printf("QuickStop "); - // if (statusWord & 0x0040) printf("SwitchOnDisabled "); - // if (statusWord & 0x0080) printf("Warning "); - // printf("]"); - // } - // printf("\n"); - // - // if (slave->Ibytes >= 6) { - // int32_t actualPosition = *(int32_t*)(slave->inputs + 2); - // printf(" -> Actual Position: %d\n", actualPosition); - // } - // } - // } - // - // if (slave->outputs && slave->Obytes > 0) { - // printf(" Outputs (%d bytes): ", slave->Obytes); - // for (int i = 0; i < slave->Obytes && i < 16; i++) { - // printf("%02X ", ((uint8_t*)slave->outputs)[i]); - // } - // if (slave->Obytes > 16) printf("..."); - // printf("\n"); - // - // if (slave->Obytes >= 2) { - // uint16_t controlWord = *(uint16_t*)slave->outputs; - // printf(" <- Control Word: 0x%04X\n", controlWord); - // } - // } - // } - // - // printf("================================================\n\n"); - // - // receiveSuccessCount = 0; - // receiveFailCount = 0; - // } - - } else { - if (cycleCount % 1000 == 0) { - printf("[Cyclic] Waiting for dorun=1, cycle=%d\n", cycleCount); - } - } - } - - printf("[Cyclic Thread] Thread exiting\n"); -} - -void EcMaster::runCheck() { - while (isRunning) { - if (dorun && (cyclicWkcErrorCount > 2 || ctx.grouplist[currentgroup].docheckstate)) { - ctx.grouplist[currentgroup].docheckstate = FALSE; - ecx_readstate(&ctx); - - for (int slaveix = 1; slaveix <= ctx.slavecount; slaveix++) { - ec_slavet *slave = &ctx.slavelist[slaveix]; - - if ((slave->group == currentgroup) && (slave->state != EC_STATE_OPERATIONAL)) { - ctx.grouplist[currentgroup].docheckstate = TRUE; - - if (slave->state == (EC_STATE_SAFE_OP + EC_STATE_ERROR)) { - printf("ERROR : slave %d is in SAFE_OP + ERROR, attempting ack.\n", slaveix); - slave->state = (EC_STATE_SAFE_OP + EC_STATE_ACK); - ecx_writestate(&ctx, slaveix); - } - else if (slave->state == EC_STATE_SAFE_OP) { - printf("WARNING : slave %d is in SAFE_OP, change to OPERATIONAL.\n", slaveix); - slave->state = EC_STATE_OPERATIONAL; - if (slave->mbxhandlerstate == ECT_MBXH_LOST) - slave->mbxhandlerstate = ECT_MBXH_CYCLIC; - ecx_writestate(&ctx, slaveix); - } - else if (slave->state > EC_STATE_NONE) { - if (ecx_reconfig_slave(&ctx, slaveix, EC_TIMEOUTMON) >= EC_STATE_PRE_OP) { - slave->islost = FALSE; - printf("MESSAGE : slave %d reconfigured\n", slaveix); - } - } - else if (!slave->islost) { - ecx_statecheck(&ctx, slaveix, EC_STATE_OPERATIONAL, EC_TIMEOUTRET); - if (slave->state == EC_STATE_NONE) { - slave->islost = TRUE; - slave->mbxhandlerstate = ECT_MBXH_LOST; - if (slave->Ibytes) { - memset(slave->inputs, 0x00, slave->Ibytes); - } - printf("ERROR : slave %d lost\n", slaveix); - } - } - } - - if (slave->islost) { - if (slave->state <= EC_STATE_INIT) { - if (ecx_recover_slave(&ctx, slaveix, EC_TIMEOUTMON)) { - slave->islost = FALSE; - printf("MESSAGE : slave %d recovered\n", slaveix); - } - } - else { - slave->islost = FALSE; - printf("MESSAGE : slave %d found\n", slaveix); - } - } - } - - if (!ctx.grouplist[currentgroup].docheckstate) { - printf("OK : all slaves resumed OPERATIONAL.\n"); - } - } - - osal_usleep(10000); - } -} - -// ==================== PDO 配置 ==================== - -int EcMaster::configurePdo(uint16_t slaveId, const EcPdo* rxPdo, const EcPdo* txPdo) { - uint8_t zero = 0; - int wkc = 0; - - // ========== 步骤1: 进入 PRE-OP ========== - printf("Step 1: Entering PRE-OP...\n"); - if (setState(slaveId, EC_STATE_PRE_OP) != 0) { - printf("Failed to enter PRE-OP\n"); - return -1; - } - printf("Slave %d in PRE-OP\n", slaveId); - osal_usleep(50000); - - // ========== 步骤2: 禁用全局 PDO 分配 ========== - printf("Step 2: Disabling global PDO assignment...\n"); - wkc = ecx_SDOwrite(&ctx, slaveId, 0x1C12, 0x00, FALSE, 1, &zero, EC_TIMEOUTRXM); - printf("ecx_SDOwrite 0x1C12, wkc = %d\n", wkc); - - wkc = ecx_SDOwrite(&ctx, slaveId, 0x1C13, 0x00, FALSE, 1, &zero, EC_TIMEOUTRXM); - printf("ecx_SDOwrite 0x1C13, wkc = %d\n", wkc); - - // ========== 计算输出大小(用于分配影子缓冲区) ========== - int outputSize = 0; - if (rxPdo) { - for (const auto& entry : rxPdo->entries) { - outputSize += (entry.bitlen + 7) / 8; - } - printf("Calculated output size: %d bytes\n", outputSize); - } - - // ========== 步骤3: 配置 RxPDO ========== - if (rxPdo && rxPdo->mapCount > 0) { - printf("Step 3: Configuring RxPDO 0x%04X\n", rxPdo->index); - - wkc = ecx_SDOwrite(&ctx, slaveId, rxPdo->index, 0x00, FALSE, 1, &zero, EC_TIMEOUTRXM); - if (wkc <= 0) { - printf("Failed to disable RxPDO 0x%04X\n", rxPdo->index); - return -1; - } - - uint8_t subidx = 1; - for (const auto& entry : rxPdo->entries) { - uint32_t mapValue = entry.makeMapValue(); - wkc = ecx_SDOwrite(&ctx, slaveId, rxPdo->index, subidx++, FALSE, 4, &mapValue, EC_TIMEOUTRXM); - if (wkc <= 0) { - printf("Failed to write RxPDO entry 0x%04X:%02X\n", entry.index, entry.subindex); - return -1; - } - printf(" Entry: 0x%04X:%02X (%d bits)\n", entry.index, entry.subindex, entry.bitlen); - } - - uint8_t mapCount = rxPdo->mapCount; - wkc = ecx_SDOwrite(&ctx, slaveId, rxPdo->index, 0x00, FALSE, 1, &mapCount, EC_TIMEOUTRXM); - if (wkc <= 0) { - printf("Failed to enable RxPDO 0x%04X\n", rxPdo->index); - return -1; - } - printf("RxPDO 0x%04X configured successfully\n", rxPdo->index); - } - - // ========== 步骤4: 配置 TxPDO ========== - if (txPdo && txPdo->mapCount > 0) { - printf("Step 4: Configuring TxPDO 0x%04X\n", txPdo->index); - - wkc = ecx_SDOwrite(&ctx, slaveId, txPdo->index, 0x00, FALSE, 1, &zero, EC_TIMEOUTRXM); - if (wkc <= 0) { - printf("Failed to disable TxPDO 0x%04X\n", txPdo->index); - return -1; - } - - uint8_t subidx = 1; - for (const auto& entry : txPdo->entries) { - uint32_t mapValue = entry.makeMapValue(); - wkc = ecx_SDOwrite(&ctx, slaveId, txPdo->index, subidx++, FALSE, 4, &mapValue, EC_TIMEOUTRXM); - if (wkc <= 0) { - printf("Failed to write TxPDO entry 0x%04X:%02X\n", entry.index, entry.subindex); - return -1; - } - printf(" Entry: 0x%04X:%02X (%d bits)\n", entry.index, entry.subindex, entry.bitlen); - } - - uint8_t mapCount = txPdo->mapCount; - wkc = ecx_SDOwrite(&ctx, slaveId, txPdo->index, 0x00, FALSE, 1, &mapCount, EC_TIMEOUTRXM); - if (wkc <= 0) { - printf("Failed to enable TxPDO 0x%04X\n", txPdo->index); - return -1; - } - printf("TxPDO 0x%04X configured successfully\n", txPdo->index); - } - - // ========== 步骤5: 应用 PDO 分配 ========== - printf("Step 5: Applying PDO assignment...\n"); - - if (rxPdo && rxPdo->mapCount > 0) { - uint8_t rpdo_map_count = 1; - wkc = ecx_SDOwrite(&ctx, slaveId, 0x1C12, 0x01, FALSE, 2, &rxPdo->index, EC_TIMEOUTRXM); - if (wkc <= 0) { - printf("Failed to write RxPDO index to 0x1C12:01\n"); - return -1; - } - - wkc = ecx_SDOwrite(&ctx, slaveId, 0x1C12, 0x00, FALSE, 1, &rpdo_map_count, EC_TIMEOUTRXM); - if (wkc <= 0) { - printf("Failed to set RxPDO count\n"); - return -1; - } - printf("RxPDO assigned to 0x1C12\n"); - } - - if (txPdo && txPdo->mapCount > 0) { - uint8_t tpdo_map_count = 1; - wkc = ecx_SDOwrite(&ctx, slaveId, 0x1C13, 0x01, FALSE, 2, &txPdo->index, EC_TIMEOUTRXM); - if (wkc <= 0) { - printf("Failed to write TxPDO index to 0x1C13:01\n"); - return -1; - } - - wkc = ecx_SDOwrite(&ctx, slaveId, 0x1C13, 0x00, FALSE, 1, &tpdo_map_count, EC_TIMEOUTRXM); - if (wkc <= 0) { - printf("Failed to set TxPDO count\n"); - return -1; - } - printf("TxPDO assigned to 0x1C13\n"); - } - - // ========== 步骤6: 为这个从站分配影子缓冲区 ========== - if (outputSize > 0) { - // 移除旧的影子缓冲区(如果存在) - auto it = shadowBuffers.find(slaveId); - if (it != shadowBuffers.end()) { - shadowBuffers.erase(it); - } - // 创建新的影子缓冲区 - shadowBuffers.emplace(std::piecewise_construct, - std::forward_as_tuple(slaveId), - std::forward_as_tuple(outputSize)); - printf("Shadow buffer allocated for slave %d: %d bytes\n", slaveId, outputSize); - } - - // ========== 步骤7: 设置 PDO 偏移 ========== - setupPdoOffsets(slaveId, rxPdo, txPdo); - - printf("PDO configuration for slave %d completed\n", slaveId); - return 0; -} - -// ========== setupMappingAndDc - 只需要做映射和DC配置 ========== -int EcMaster::setupMappingAndDc() { - printf("\n========== Step 6: Finalizing mapping and DC configuration ==========\n"); - - // 配置映射 - ctx.manualstatechange = 0; - ecx_config_map_group(&ctx, IOmap, 0); - - // 设置 mappingdone 标志 - mappingdone = 1; - - // 验证并保存所有从站的 PDO 数据指针和偏移 - for (int i = 1; i <= ctx.slavecount; i++) { - inputs[i] = ctx.slavelist[i].inputs; - outputs[i] = ctx.slavelist[i].outputs; - inputSizes[i] = ctx.slavelist[i].Ibytes; - outputSizes[i] = ctx.slavelist[i].Obytes; - - if (outputs[i]) { - outputOffsets[i] = (uint8_t*)outputs[i] - IOmap; - printf("Slave %d: Out offset=%d, size=%d bytes\n", - i, outputOffsets[i], outputSizes[i]); - } - printf("Slave %d: In=%d bytes, Out=%d bytes\n", i, inputSizes[i], outputSizes[i]); - - printf(" SM2 (Outputs): StartAddr=0x%04X, Length=%d\n", - ctx.slavelist[i].SM[2].StartAddr, ctx.slavelist[i].SM[2].SMlength); - printf(" SM3 (Inputs): StartAddr=0x%04X, Length=%d\n", - ctx.slavelist[i].SM[3].StartAddr, ctx.slavelist[i].SM[3].SMlength); - } - - expectedWKC = (ctx.grouplist[0].outputsWKC * 2) + ctx.grouplist[0].inputsWKC; - printf("Expected WKC: %d\n", expectedWKC); - - // 配置分布式时钟 - ecx_configdc(&ctx); - - // 添加所有 CoE 从站到周期邮箱处理 - for (int i = 1; i <= ctx.slavecount; i++) { - if (ctx.slavelist[i].CoEdetails > 0) { - ecx_slavembxcyclic(&ctx, i); - printf("Slave %d added to cyclic mailbox handler\n", i); - } - } - - // 验证影子缓冲区大小是否匹配实际输出大小 - for (int i = 1; i <= ctx.slavecount; i++) { - auto it = shadowBuffers.find(i); - if (it != shadowBuffers.end()) { - size_t shadowSize = it->second.size; - int actualSize = outputSizes[i]; - if (shadowSize != (size_t)actualSize) { - printf("WARNING: Slave %d shadow buffer size (%zu) != actual output size (%d)\n", - i, shadowSize, actualSize); - // 重新分配匹配的大小 - it->second = SlaveShadowBuffer(actualSize); - printf(" Reallocated shadow buffer to %d bytes\n", actualSize); - } - } else if (outputSizes[i] > 0) { - // 如果没有影子缓冲区但有输出数据,创建一个 - shadowBuffers.emplace(std::piecewise_construct, - std::forward_as_tuple(i), - std::forward_as_tuple(outputSizes[i])); - printf("Created shadow buffer for slave %d: %d bytes\n", i, outputSizes[i]); - } - } - - printf("Mapping and DC configuration completed\n"); - return 0; -} - -int EcMaster::setupPdoOffsets(uint16_t slaveId, const EcPdo* rxPdo, const EcPdo* txPdo) { - EcPdoOffsets offsets; - memset(&offsets, 0xFF, sizeof(offsets)); - - // ========== 解析 RxPDO (Outputs) ========== - if (rxPdo) { - uint16_t offset = 0; - printf("RxPDO 0x%04X mapping (%zu entries):\n", rxPdo->index, rxPdo->entries.size()); - - for (const auto& entry : rxPdo->entries) { - uint8_t bytes = (entry.bitlen + 7) / 8; - printf(" Entry 0x%04X:%02X -> offset %d (%d bytes)\n", - entry.index, entry.subindex, offset, bytes); - - switch (entry.index) { - case 0x6040: // Control Word - offsets.controlWord = offset; - printf(" *** Control Word at offset %d ***\n", offset); - break; - case 0x607A: // Target Position - offsets.targetPosition = offset; - break; - case 0x60FF: // Target Velocity - offsets.targetVelocity = offset; - break; - case 0x6071: // Target Torque - offsets.targetTorque = offset; - break; - case 0x6083: // Profile Acceleration - offsets.profileAcceleration = offset; - printf(" *** Profile Acceleration at offset %d ***\n", offset); - break; - case 0x6084: // Profile Deceleration - offsets.profileDeceleration = offset; - printf(" *** Profile Deceleration at offset %d ***\n", offset); - break; - case 0x6081: // Profile Velocity - offsets.profileVelocity = offset; - printf(" *** Profile Velocity at offset %d ***\n", offset); - break; - case 0x6087: // Torque Slope - offsets.torqueSlope = offset; - break; - case 0x6060: // Mode of Operation - offsets.modeOfOperation = offset; - break; - default: - printf(" *** Unknown entry 0x%04X:%02X ***\n", entry.index, entry.subindex); - break; - } - offset += bytes; - } - } - - // ========== 解析 TxPDO (Inputs) ========== - if (txPdo) { - uint16_t offset = 0; - printf("\nTxPDO 0x%04X mapping (%zu entries):\n", txPdo->index, txPdo->entries.size()); - - for (const auto& entry : txPdo->entries) { - uint8_t bytes = (entry.bitlen + 7) / 8; - printf(" Entry 0x%04X:%02X -> offset %d (%d bytes)\n", - entry.index, entry.subindex, offset, bytes); - - switch (entry.index) { - case 0x6041: // Status Word - offsets.statusWord = offset; - printf(" *** Status Word at offset %d ***\n", offset); - break; - case 0x6064: // Actual Position - offsets.actualPosition = offset; - break; - case 0x606C: // Actual Velocity - offsets.actualVelocity = offset; - break; - case 0x6077: // Actual Torque - offsets.actualTorque = offset; - break; - case 0x6061: // Mode of Operation Display - offsets.modeOfOperationDisplay = offset; - break; - case 0x603F: // Error Code - offsets.errorCode = offset; - break; - case 0x6078: // Actual Current - offsets.actualCurrent = offset; - printf(" *** Actual Current at offset %d ***\n", offset); - break; - default: - printf(" *** Unknown entry 0x%04X:%02X ***\n", entry.index, entry.subindex); - break; - } - offset += bytes; - } - } - - pdoOffsets[slaveId] = offsets; - - // ========== 打印摘要 ========== - printf("\n=== PDO Offset Summary ===\n"); - printf("RxPDO (Outputs):\n"); - printf(" Control Word: %d %s\n", offsets.controlWord, - offsets.controlWord == 0xFFFF ? "(INVALID)" : ""); - printf(" Target Position: %d %s\n", offsets.targetPosition, - offsets.targetPosition == 0xFFFF ? "(INVALID)" : ""); - printf(" Target Velocity: %d %s\n", offsets.targetVelocity, - offsets.targetVelocity == 0xFFFF ? "(INVALID)" : ""); - printf(" Target Torque: %d %s\n", offsets.targetTorque, - offsets.targetTorque == 0xFFFF ? "(INVALID)" : ""); - printf(" Profile Velocity: %d %s\n", offsets.profileVelocity, - offsets.profileVelocity == 0xFFFF ? "(NOT IN PDO)" : ""); - printf(" Profile Acc: %d %s\n", offsets.profileAcceleration, - offsets.profileAcceleration == 0xFFFF ? "(NOT IN PDO)" : ""); - printf(" Profile Dec: %d %s\n", offsets.profileDeceleration, - offsets.profileDeceleration == 0xFFFF ? "(NOT IN PDO)" : ""); - printf(" Mode of Operation: %d %s\n", offsets.modeOfOperation, - offsets.modeOfOperation == 0xFFFF ? "(INVALID)" : ""); - - printf("\nTxPDO (Inputs):\n"); - printf(" Status Word: %d %s\n", offsets.statusWord, - offsets.statusWord == 0xFFFF ? "(INVALID)" : ""); - printf(" Actual Position: %d %s\n", offsets.actualPosition, - offsets.actualPosition == 0xFFFF ? "(INVALID)" : ""); - printf(" Actual Velocity: %d %s\n", offsets.actualVelocity, - offsets.actualVelocity == 0xFFFF ? "(INVALID)" : ""); - printf(" Actual Torque: %d %s\n", offsets.actualTorque, - offsets.actualTorque == 0xFFFF ? "(INVALID)" : ""); - printf(" Mode Display: %d %s\n", offsets.modeOfOperationDisplay, - offsets.modeOfOperationDisplay == 0xFFFF ? "(INVALID)" : ""); - printf(" Error Code: %d %s\n", offsets.errorCode, - offsets.errorCode == 0xFFFF ? "(NOT IN PDO)" : ""); - printf(" Actual Current: %d %s\n", offsets.actualCurrent, - offsets.actualCurrent == 0xFFFF ? "(NOT IN PDO)" : ""); - printf("=================================\n"); - - if (offsets.controlWord == 0xFFFF || offsets.statusWord == 0xFFFF) { - printf("ERROR: Critical PDO offsets not configured!\n"); - return -1; - } - - return 0; -} - -// ==================== PDO 访问接口 ==================== - -void EcMaster::setControlWord(uint16_t slaveId, uint16_t value) { - if (inOp) { - writePdo(slaveId, pdoOffsets[slaveId].controlWord, value); - } else { - writeSdo(slaveId, 0x6040, 0, &value, EcDataType::Uint16, 100); - } -} - -uint16_t EcMaster::getStatusWord(uint16_t slaveId) { - if (inOp) { - return readPdo(slaveId, pdoOffsets[slaveId].statusWord); - } - uint16_t value; - readSdo(slaveId, 0x6041, 0, &value, EcDataType::Uint16, 100); - return value; -} - -void EcMaster::setTargetPosition(uint16_t slaveId, int32_t value) { - if (inOp) { - writePdo(slaveId, pdoOffsets[slaveId].targetPosition, value); - } else { - writeSdo(slaveId, 0x607A, 0, &value, EcDataType::Int32, 100); - } -} - -int32_t EcMaster::getActualPosition(uint16_t slaveId) { - if (inOp) { - return readPdo(slaveId, pdoOffsets[slaveId].actualPosition); - } - int32_t value; - readSdo(slaveId, 0x6064, 0, &value, EcDataType::Int32, 100); - return value; -} - -void EcMaster::setTargetVelocity(uint16_t slaveId, int32_t value) { - if (inOp) { - writePdo(slaveId, pdoOffsets[slaveId].targetVelocity, value); - } else { - writeSdo(slaveId, 0x60FF, 0, &value, EcDataType::Int32, 100); - } -} - -int32_t EcMaster::getActualVelocity(uint16_t slaveId) { - if (inOp) { - return readPdo(slaveId, pdoOffsets[slaveId].actualVelocity); - } - int32_t value; - readSdo(slaveId, 0x606C, 0, &value, EcDataType::Int32, 100); - return value; -} - -void EcMaster::setTargetTorque(uint16_t slaveId, int16_t value) { - if (inOp) { - writePdo(slaveId, pdoOffsets[slaveId].targetTorque, value); - } else { - writeSdo(slaveId, 0x6071, 0, &value, EcDataType::Int16, 100); - } -} - -int16_t EcMaster::getActualTorque(uint16_t slaveId) { - if (inOp) { - return readPdo(slaveId, pdoOffsets[slaveId].actualTorque); - } - int16_t value; - readSdo(slaveId, 0x6077, 0, &value, EcDataType::Int16, 100); - return value; -} - -void EcMaster::setModeOfOperation(uint16_t slaveId, int8_t value) { - if (inOp) { - writePdo(slaveId, pdoOffsets[slaveId].modeOfOperation, value); - } else { - writeSdo(slaveId, 0x6060, 0, &value, EcDataType::Int8, 100); - } -} - -int8_t EcMaster::getModeOfOperationDisplay(uint16_t slaveId) { - if (inOp) { - return readPdo(slaveId, pdoOffsets[slaveId].modeOfOperationDisplay); - } - int8_t value; - readSdo(slaveId, 0x6061, 0, &value, EcDataType::Int8, 100); - return value; -} - -void EcMaster::setProfileVelocity(uint16_t slaveId, uint32_t value) { - if (inOp) { - writePdo(slaveId, pdoOffsets[slaveId].profileVelocity, value); - } -} - -void EcMaster::setProfileAcceleration(uint16_t slaveId, uint32_t value) { - if (inOp) { - writePdo(slaveId, pdoOffsets[slaveId].profileAcceleration, value); - } -} - -void EcMaster::setProfileDeceleration(uint16_t slaveId, uint32_t value) { - if (inOp) { - writePdo(slaveId, pdoOffsets[slaveId].profileDeceleration, value); - } -} - -int16_t EcMaster::getCurrentValue(uint16_t slaveId) { - if (inOp) { - return readPdo(slaveId, pdoOffsets[slaveId].actualCurrent); - } - int16_t value; - readSdo(slaveId, 0x6078, 0, &value, EcDataType::Int16, 100); - return value; -} - -uint16_t EcMaster::getErrorCode(uint16_t slaveId) { - if (inOp) { - auto it = pdoOffsets.find(slaveId); - if (it != pdoOffsets.end() && it->second.errorCode != 0xFFFF) { - return readPdo(slaveId, it->second.errorCode); - } - } - uint16_t value = 0; - readSdo(slaveId, 0x603F, 0, &value, EcDataType::Uint16, 100); - return value; -} - -// ==================== 电机控制高级接口 ==================== - -int EcMaster::enable(uint16_t slaveId) { - if (inOp) { - setControlWord(slaveId, 0x0006); - osal_usleep(10000); - setControlWord(slaveId, 0x0007); - osal_usleep(10000); - setControlWord(slaveId, 0x000F); - } else { - uint16_t val = 0x0006; - writeSdo(slaveId, 0x6040, 0, &val, EcDataType::Uint16, 100); - osal_usleep(10000); - val = 0x0007; - writeSdo(slaveId, 0x6040, 0, &val, EcDataType::Uint16, 100); - osal_usleep(10000); - val = 0x000F; - writeSdo(slaveId, 0x6040, 0, &val, EcDataType::Uint16, 100); - } - osal_usleep(10000); - return 0; -} - -int EcMaster::disable(uint16_t slaveId) { - if (inOp) { - setControlWord(slaveId, 0x0007); - osal_usleep(10000); - setControlWord(slaveId, 0x0000); - } else { - uint16_t val = 0x0007; - writeSdo(slaveId, 0x6040, 0, &val, EcDataType::Uint16, 100); - osal_usleep(10000); - val = 0x0000; - writeSdo(slaveId, 0x6040, 0, &val, EcDataType::Uint16, 100); - } - return 0; -} - -int EcMaster::faultReset(uint16_t slaveId) { - if (inOp) { - setControlWord(slaveId, 0x0080); - osal_usleep(10000); - setControlWord(slaveId, 0x0000); - } else { - uint16_t val = 0x0080; - writeSdo(slaveId, 0x6040, 0, &val, EcDataType::Uint16, 100); - osal_usleep(10000); - val = 0x0000; - writeSdo(slaveId, 0x6040, 0, &val, EcDataType::Uint16, 100); - } - return 0; -} - -// ==================== SDO 读写 ==================== - -int EcMaster::readSdo(uint16_t slaveId, uint16_t index, uint8_t subindex, void* value, EcDataType type, int timeoutMs) { - int size = 0; - switch (type) { - case EcDataType::Boolean: - case EcDataType::Int8: - case EcDataType::Uint8: - size = 1; break; - case EcDataType::Int16: - case EcDataType::Uint16: - size = 2; break; - case EcDataType::Int32: - case EcDataType::Uint32: - case EcDataType::Real32: - size = 4; break; - case EcDataType::Real64: - size = 8; break; - default: return -1; - } - int wkc = ecx_SDOread(&ctx, slaveId, index, subindex, FALSE, &size, value, timeoutMs); - return wkc > 0 ? 0 : -1; -} - -int EcMaster::writeSdo(uint16_t slaveId, uint16_t index, uint8_t subindex, void* value, EcDataType type, int timeoutMs) { - int size = 0; - switch (type) { - case EcDataType::Boolean: - case EcDataType::Int8: - case EcDataType::Uint8: - size = 1; break; - case EcDataType::Int16: - case EcDataType::Uint16: - size = 2; break; - case EcDataType::Int32: - case EcDataType::Uint32: - case EcDataType::Real32: - size = 4; break; - case EcDataType::Real64: - size = 8; break; - default: return -1; - } - int wkc = ecx_SDOwrite(&ctx, slaveId, index, subindex, FALSE, size, value, timeoutMs); - return wkc > 0 ? 0 : -1; -} - -// ==================== SM/FMMU 配置校验 ==================== - -void EcMaster::verifySmConfiguration(uint16_t slaveId) { - if (slaveId > (uint16_t)ctx.slavecount) { - printf("Invalid slave ID: %d\n", slaveId); - return; - } - - ec_slavet* slave = &ctx.slavelist[slaveId]; - - printf("\n========== SM Configuration for Slave %d ==========\n", slaveId); - - printf("PDO Mapping Sizes:\n"); - printf(" Outputs (Obytes): %d bytes\n", slave->Obytes); - printf(" Inputs (Ibytes): %d bytes\n", slave->Ibytes); - - printf("\nSync Manager Configuration:\n"); - const char* sm_names[] = {"MBoxOut", "MBoxIn", "Outputs", "Inputs"}; - for (int sm = 0; sm < 4; sm++) { - printf(" SM%d (%s): StartAddr=0x%04X, Length=%d bytes\n", - sm, sm_names[sm], - slave->SM[sm].StartAddr, - slave->SM[sm].SMlength); - } - - printf("\nFMMU Configuration:\n"); - printf(" Outputs mapped to IOmap at offset: %td\n", - (uint8_t*)slave->outputs - IOmap); - printf(" Inputs mapped to IOmap at offset: %td\n", - (uint8_t*)slave->inputs - IOmap); - - // 打印影子缓冲区信息 - auto it = shadowBuffers.find(slaveId); - if (it != shadowBuffers.end()) { - printf("\nShadow Buffer:\n"); - printf(" Size: %zu bytes\n", it->second.size); - printf(" Dirty: %s\n", it->second.dirty ? "true" : "false"); - } else { - printf("\nShadow Buffer: Not allocated\n"); - } - - printf("==================================================\n"); -} - -void EcMaster::verifyAllSlavesSmConfiguration() { - for (int i = 1; i <= ctx.slavecount; i++) { - verifySmConfiguration((uint16_t)i); - } -} \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_master.h b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_master.h deleted file mode 100644 index e4b67ad0..00000000 --- a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_master.h +++ /dev/null @@ -1,328 +0,0 @@ -// ec_master.h -#ifndef EC_MASTER_H -#define EC_MASTER_H - -#include "ec_types.h" -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif -#include "soem/soem.h" -#include "soem/osal.h" -#ifdef __cplusplus -} -#endif - -// 错误码定义 -#define EC_SUCCESS 0 -#define EC_FAILED_INIT 1 -#define EC_FAILED_NOSLAVE 2 - -#ifndef EC_TIMEOUTMON -#define EC_TIMEOUTMON 500 -#endif - -#ifndef EC_TIMEOUTRET -#define EC_TIMEOUTRET 2000 -#endif - -#ifndef EC_TIMEOUTSTATE -#define EC_TIMEOUTSTATE 2000 -#endif - -#ifndef EC_TIMEOUTRXM -#define EC_TIMEOUTRXM 2000 -#endif - -// EtherCAT 主站类 -class EcMaster { - -public: - // 操作模式枚举(符合 CiA 402 标准) - enum EcOperationMode : int8_t { - // 轮廓模式 (Profile Modes) - EcOperationMode_ProfilePosition = 1, // PP 模式 - EcOperationMode_ProfileVelocity = 3, // PV 模式 - EcOperationMode_ProfileTorque = 4, // PT 模式 - - // 插补模式 - EcOperationMode_InterpolatedPosition = 7, // IP 模式 - - // 循环同步模式 (Cyclic Sync Modes) - EcOperationMode_CyclicSyncPosition = 8, // CSP 模式 - EcOperationMode_CyclicSyncVelocity = 9, // CSV 模式 - EcOperationMode_CyclicSyncTorque = 10, // CST 模式 - }; - -public: - EcMaster(); - ~EcMaster(); - - EcMaster(const EcMaster&) = delete; - EcMaster& operator=(const EcMaster&) = delete; - - // 初始化 - int init(const char* ifName, int cycleTimeUs = 1000); - void close() { shutdown(); } - int getSlaveCount() const { return slaveCount; } - bool isOperational() const { return inOp; } - - // 状态控制 - int setState(uint16_t slaveId, uint8_t targetState); - int preOperational(uint16_t slaveId) { return setState(slaveId, EC_STATE_PRE_OP); } - int safeOperational(uint16_t slaveId) { return setState(slaveId, EC_STATE_SAFE_OP); } - int operational(uint16_t slaveId) { return setState(slaveId, EC_STATE_OPERATIONAL); } - int gotoOperational(); - void shutdown(); - - // PDO 配置 - int configurePdo(uint16_t slaveId, const EcPdo* rxPdo, const EcPdo* txPdo); - int setupMappingAndDc(); // 配置所有从站的映射和DC - int setupPdoOffsets(uint16_t slaveId, const EcPdo* rxPdo, const EcPdo* txPdo); - - // PDO 数据访问(高性能,零拷贝) - void setControlWord(uint16_t slaveId, uint16_t value); - uint16_t getStatusWord(uint16_t slaveId); - void setTargetPosition(uint16_t slaveId, int32_t value); - int32_t getActualPosition(uint16_t slaveId); - void setTargetVelocity(uint16_t slaveId, int32_t value); - int32_t getActualVelocity(uint16_t slaveId); - void setTargetTorque(uint16_t slaveId, int16_t value); - int16_t getActualTorque(uint16_t slaveId); - void setModeOfOperation(uint16_t slaveId, int8_t value); - int8_t getModeOfOperationDisplay(uint16_t slaveId); - - void setProfileVelocity(uint16_t slaveId, uint32_t value); - void setProfileAcceleration(uint16_t slaveId, uint32_t value); - void setProfileDeceleration(uint16_t slaveId, uint32_t value); - int16_t getCurrentValue(uint16_t slaveId); - uint16_t getErrorCode(uint16_t slaveId); - - // 电机控制高级接口 - int enable(uint16_t slaveId); - int disable(uint16_t slaveId); - int faultReset(uint16_t slaveId); - - // 周期操作 - int sendOutputs() { return ecx_send_processdata(&ctx); } - int receiveInputs() { return ecx_receive_processdata(&ctx, EC_TIMEOUTRET); } - - // 影子缓冲区同步接口 - void syncSlaveOutputs(uint16_t slaveId); // 同步指定从站的输出 - void syncAllOutputs(); // 同步所有从站的输出 - - // 原始数据访问 - uint8_t* getInputs(uint16_t slaveId) { - auto it = inputs.find(slaveId); - return (it != inputs.end()) ? it->second : nullptr; - } - uint8_t* getOutputs(uint16_t slaveId) { - auto it = outputs.find(slaveId); - return (it != outputs.end()) ? it->second : nullptr; - } - int getInputSize(uint16_t slaveId) { - auto it = inputSizes.find(slaveId); - return (it != inputSizes.end()) ? it->second : 0; - } - int getOutputSize(uint16_t slaveId) { - auto it = outputSizes.find(slaveId); - return (it != outputSizes.end()) ? it->second : 0; - } - - // SDO 读写(仅用于配置阶段) - int readSdo(uint16_t slaveId, uint16_t index, uint8_t subindex, void* value, EcDataType type, int timeoutMs = 100); - int writeSdo(uint16_t slaveId, uint16_t index, uint8_t subindex, void* value, EcDataType type, int timeoutMs = 100); - - // SM/FMMU 配置校验(调试用) - void verifySmConfiguration(uint16_t slaveId); - void verifyAllSlavesSmConfiguration(); - - // 模板便捷接口 - template - int readSdo(uint16_t slaveId, uint16_t index, uint8_t subindex, T* value, int timeoutMs = 100) { - EcDataType type; - if constexpr (std::is_same_v) type = EcDataType::Int8; - else if constexpr (std::is_same_v) type = EcDataType::Uint8; - else if constexpr (std::is_same_v) type = EcDataType::Int16; - else if constexpr (std::is_same_v) type = EcDataType::Uint16; - else if constexpr (std::is_same_v) type = EcDataType::Int32; - else if constexpr (std::is_same_v) type = EcDataType::Uint32; - else if constexpr (std::is_same_v) type = EcDataType::Real32; - else if constexpr (std::is_same_v) type = EcDataType::Real64; - else return -1; - return readSdo(slaveId, index, subindex, value, type, timeoutMs); - } - - template - int writeSdo(uint16_t slaveId, uint16_t index, uint8_t subindex, T* value, int timeoutMs = 100) { - EcDataType type; - if constexpr (std::is_same_v) type = EcDataType::Int8; - else if constexpr (std::is_same_v) type = EcDataType::Uint8; - else if constexpr (std::is_same_v) type = EcDataType::Int16; - else if constexpr (std::is_same_v) type = EcDataType::Uint16; - else if constexpr (std::is_same_v) type = EcDataType::Int32; - else if constexpr (std::is_same_v) type = EcDataType::Uint32; - else if constexpr (std::is_same_v) type = EcDataType::Real32; - else if constexpr (std::is_same_v) type = EcDataType::Real64; - else return -1; - return writeSdo(slaveId, index, subindex, value, type, timeoutMs); - } - -private: - // ========== 影子缓冲区结构(每个从站独立) ========== - struct SlaveShadowBuffer { - uint8_t* data = nullptr; // 影子缓冲区数据指针 - size_t size = 0; // 缓冲区大小 - bool dirty = false; // 是否有待同步的数据 - - SlaveShadowBuffer() = default; - - explicit SlaveShadowBuffer(size_t sz) : size(sz) { - if (sz > 0) { - data = new uint8_t[sz]; - memset(data, 0, sz); - } - } - - ~SlaveShadowBuffer() { - delete[] data; - data = nullptr; - } - - // 禁止拷贝 - SlaveShadowBuffer(const SlaveShadowBuffer&) = delete; - SlaveShadowBuffer& operator=(const SlaveShadowBuffer&) = delete; - - // 允许移动 - SlaveShadowBuffer(SlaveShadowBuffer&& other) noexcept - : data(other.data), size(other.size), dirty(other.dirty) { - other.data = nullptr; - other.size = 0; - other.dirty = false; - } - - SlaveShadowBuffer& operator=(SlaveShadowBuffer&& other) noexcept { - if (this != &other) { - delete[] data; - data = other.data; - size = other.size; - dirty = other.dirty; - other.data = nullptr; - other.size = 0; - other.dirty = false; - } - return *this; - } - - // 重新分配缓冲区 - void reallocate(size_t newSize) { - if (data) { - delete[] data; - data = nullptr; - } - size = newSize; - if (newSize > 0) { - data = new uint8_t[newSize]; - memset(data, 0, newSize); - } - dirty = false; - } - }; - - // SOEM 上下文 - ecx_contextt ctx; - uint8_t IOmap[4096]; - - // 每个从站的影子缓冲区映射 - std::map shadowBuffers; - std::atomic anyPdoDirty{false}; // 是否有任何影子缓冲区有更新 - - int slaveCount = 0; - int expectedWKC = 0; - int currentgroup = 0; - - // 状态标志 - std::atomic inOp{false}; - std::atomic isRunning{false}; - std::atomic mappingdone{0}; - std::atomic dorun{0}; - std::atomic cyclicWkcErrorCount{0}; - - int64_t cycleTimeNs = 1000000; - - OSAL_THREAD_HANDLE cyclicThread; - OSAL_THREAD_HANDLE checkThread; - - // PDO 数据指针映射 - std::map inputs; - std::map outputs; - std::map inputSizes; - std::map outputSizes; - std::map outputOffsets; - - // PDO 偏移映射 - std::map pdoOffsets; - - // 辅助函数 - template - void writePdo(uint16_t slaveId, uint16_t offset, T value); - - template - T readPdo(uint16_t slaveId, uint16_t offset); - - // 获取从站的影子缓冲区 - SlaveShadowBuffer* getShadowBuffer(uint16_t slaveId); - - // 线程函数 - static OSAL_THREAD_FUNC_RT cyclicThreadFunc(void* arg); - static OSAL_THREAD_FUNC checkThreadFunc(void* arg); - void runCyclic(); - void runCheck(); -}; - -// ==================== 模板实现(必须放在头文件中) ==================== - -template -void EcMaster::writePdo(uint16_t slaveId, uint16_t offset, T value) { - if (inOp) { - auto* shadow = getShadowBuffer(slaveId); - if (shadow && shadow->data) { - if (offset + sizeof(T) <= shadow->size) { - *(T*)(shadow->data + offset) = value; - shadow->dirty = true; - anyPdoDirty = true; - return; - } - } - } - - // 降级:直接写 IOmap(配置阶段使用) - auto outIt = outputs.find(slaveId); - if (outIt != outputs.end() && outIt->second) { - auto sizeIt = outputSizes.find(slaveId); - if (sizeIt != outputSizes.end() && offset + sizeof(T) <= (uint16_t)sizeIt->second) { - *(T*)(outIt->second + offset) = value; - } - } -} - -template -T EcMaster::readPdo(uint16_t slaveId, uint16_t offset) { - auto it = pdoOffsets.find(slaveId); - if (it == pdoOffsets.end() || offset == 0xFFFF) return 0; - - auto inIt = inputs.find(slaveId); - if (inIt != inputs.end() && inIt->second) { - auto sizeIt = inputSizes.find(slaveId); - if (sizeIt != inputSizes.end() && offset + sizeof(T) <= (uint16_t)sizeIt->second) { - return *(T*)(inIt->second + offset); - } - } - return 0; -} - -#endif // EC_MASTER_H \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_pdo_config.h b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_pdo_config.h deleted file mode 100644 index 7f8a919d..00000000 --- a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_pdo_config.h +++ /dev/null @@ -1,75 +0,0 @@ -// ec_pdo_config.h - 固定 PDO 配置(替代 XML 解析) -#ifndef EC_PDO_CONFIG_H -#define EC_PDO_CONFIG_H - -#include "ec_types.h" - -// 创建固定的 RxPDO 配置(对应 XML 中的 #x1600) -inline EcPdo createFixedRxPdo() { - EcPdo rxPdo; - rxPdo.index = 0x1600; - rxPdo.mapCount = 10; // 9个有效条目 + 1个结束标志 - rxPdo.name = "Outputs"; - rxPdo.isRxPdo = true; - - // 按XML顺序填充 Entry - rxPdo.entries.push_back({0x6040, 0, 16, "Control Word", EcDataType::Uint16}); - rxPdo.entries.push_back({0x607A, 0, 32, "Target Position", EcDataType::Int32}); - rxPdo.entries.push_back({0x60FF, 0, 32, "Target Velocity", EcDataType::Int32}); - rxPdo.entries.push_back({0x6071, 0, 16, "Target Torque", EcDataType::Int16}); - rxPdo.entries.push_back({0x6083, 0, 32, "Profile Acceleration", EcDataType::Uint32}); - rxPdo.entries.push_back({0x6084, 0, 32, "Profile Deceleration", EcDataType::Uint32}); - rxPdo.entries.push_back({0x6081, 0, 32, "Profile Velocity", EcDataType::Uint32}); - rxPdo.entries.push_back({0x6087, 0, 32, "Torque Slope", EcDataType::Uint32}); - rxPdo.entries.push_back({0x6060, 0, 8, "Modes Of Operation", EcDataType::Int8}); - rxPdo.entries.push_back({0x0000, 0, 8, "End Marker", EcDataType::Uint8}); - - return rxPdo; -} - -// 创建固定的 TxPDO 配置(对应 XML 中的 #x1a00) -inline EcPdo createFixedTxPdo() { - EcPdo txPdo; - txPdo.index = 0x1A00; - txPdo.mapCount = 8; // 7个有效条目 + 1个结束标志 - txPdo.name = "Inputs"; - txPdo.isRxPdo = false; - - // 按XML顺序填充 Entry - txPdo.entries.push_back({0x6041, 0, 16, "Status Word", EcDataType::Uint16}); - txPdo.entries.push_back({0x6064, 0, 32, "Actual Position", EcDataType::Int32}); - txPdo.entries.push_back({0x606C, 0, 32, "Actual Velocity", EcDataType::Int32}); - txPdo.entries.push_back({0x6077, 0, 16, "Actual Torque", EcDataType::Int16}); - txPdo.entries.push_back({0x6061, 0, 8, "Mode Of Operation Display", EcDataType::Int8}); - txPdo.entries.push_back({0x603F, 0, 16, "Error Code", EcDataType::Uint16}); - txPdo.entries.push_back({0x6078, 0, 16, "Actual Current Value", EcDataType::Int16}); - txPdo.entries.push_back({0x0000, 0, 8, "End Marker", EcDataType::Uint8}); - - return txPdo; -} - -// 创建完整的从站配置 - 可以直接替换 XML 解析结果 -inline EcSlaveConfig createFixedSlaveConfig(uint16_t slaveId) { - EcSlaveConfig config; - config.slaveId = slaveId; - config.productCode = 0x00000000; - config.revisionNo = 0; - config.name = "Axis (csv,csp,cst)"; - - EcModule module; - module.moduleIdent = 0x119800; // 对应 XML 中的 ModuleIdent="#x119800" - module.name = "dynamic switchbewteen csp/csv"; - - module.rxPdos.push_back(createFixedRxPdo()); - module.txPdos.push_back(createFixedTxPdo()); - - // 记录可用的PDO索引(对应 parser.getAvailableRxPdos/getAvailableTxPdos 的返回值) - config.availableRxPdos.push_back(0x1600); - config.availableTxPdos.push_back(0x1A00); - - config.modules.push_back(module); - - return config; -} - -#endif // EC_PDO_CONFIG_H \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_types.h b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_types.h deleted file mode 100644 index 174f2460..00000000 --- a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_types.h +++ /dev/null @@ -1,111 +0,0 @@ -// ec_types.h - 公共类型定义 -#ifndef EC_TYPES_H -#define EC_TYPES_H - -#include -#include -#include - -// 数据类型枚举 -enum class EcDataType : uint8_t { - Boolean = 0x01, - Int8 = 0x02, - Int16 = 0x03, - Int32 = 0x04, - Uint8 = 0x05, - Uint16 = 0x06, - Uint32 = 0x07, - Real32 = 0x08, - Real64 = 0x09 -}; - -// 从站状态枚举 -enum class EcState : uint8_t { - Init = 0x01, - PreOp = 0x02, - SafeOp = 0x04, - Operational = 0x08 -}; - -// 操作模式枚举 -enum class EcOperateMode : int8_t { - ProfilePosition = 1, - Velocity = 2, - ProfileVelocity = 3, - ProfileTorque = 4, - Homing = 6, - CyclicSyncPosition = 8, - CyclicSyncVelocity = 9, - CyclicSyncTorque = 10 -}; - -// PDO 条目 -struct EcPdoEntry { - uint16_t index = 0; - uint8_t subindex = 0; - uint8_t bitlen = 0; - std::string name; - EcDataType dataType = EcDataType::Uint8; - - uint32_t makeMapValue() const { - return ((uint32_t)index << 16) | ((uint32_t)subindex << 8) | bitlen; - } -}; - -// PDO -struct EcPdo { - uint16_t index = 0; - uint8_t mapCount = 0; - std::vector entries; - std::string name; - bool isRxPdo = false; - - void updateMapCount() { - mapCount = entries.size(); - } -}; - -// 模块信息 -struct EcModule { - uint32_t moduleIdent = 0; - std::string name; - std::vector rxPdos; - std::vector txPdos; -}; - -// 从站配置信息 -struct EcSlaveConfig { - uint16_t slaveId = 0; - uint32_t productCode = 0; - uint32_t revisionNo = 0; - std::string name; - std::vector modules; - std::vector availableRxPdos; - std::vector availableTxPdos; -}; - -// PDO 偏移映射 -// ec_types.h 或 ec_master.h 中 -struct EcPdoOffsets { - // RxPDO 偏移量 (主站 -> 从站) - uint16_t controlWord = 0xFFFF; // 0x6040 - uint16_t targetPosition = 0xFFFF; // 0x607A - uint16_t targetVelocity = 0xFFFF; // 0x60FF - uint16_t targetTorque = 0xFFFF; // 0x6071 - uint16_t profileAcceleration = 0xFFFF; // 0x6083 (PP模式) - uint16_t profileDeceleration = 0xFFFF; // 0x6084 (PP模式) - uint16_t profileVelocity = 0xFFFF; // 0x6081 (PP模式) - uint16_t torqueSlope = 0xFFFF; // 0x6087 (PT模式) - uint16_t modeOfOperation = 0xFFFF; // 0x6060 - - // TxPDO 偏移量 (从站 -> 主站) - uint16_t statusWord = 0xFFFF; // 0x6041 - uint16_t actualPosition = 0xFFFF; // 0x6064 - uint16_t actualVelocity = 0xFFFF; // 0x606C - uint16_t actualTorque = 0xFFFF; // 0x6077 - uint16_t modeOfOperationDisplay = 0xFFFF;// 0x6061 - uint16_t errorCode = 0xFFFF; // 0x603F - uint16_t actualCurrent = 0xFFFF; // 0x6078 当前电流值 -}; - -#endif // EC_TYPES_H \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_xml_parser.cpp b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_xml_parser.cpp deleted file mode 100644 index c721e3fe..00000000 --- a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_xml_parser.cpp +++ /dev/null @@ -1,317 +0,0 @@ -// ec_xml_parser.cpp -#include "ec_xml_parser.h" -#include "rapidxml/xml_parser.h" -#include -#include -#include - -using namespace rapidxml; - -static EcDataType stringToDataType(const std::string& typeName) { - if (typeName == "BOOL") return EcDataType::Boolean; - if (typeName == "SINT") return EcDataType::Int8; - if (typeName == "INT") return EcDataType::Int16; - if (typeName == "DINT") return EcDataType::Int32; - if (typeName == "USINT") return EcDataType::Uint8; - if (typeName == "UINT") return EcDataType::Uint16; - if (typeName == "UDINT") return EcDataType::Uint32; - if (typeName == "REAL") return EcDataType::Real32; - return EcDataType::Uint8; -} - -static uint32_t parseHexValue(const std::string& str) { - if (str.empty()) return 0; - if (str[0] == '#' && str[1] == 'x') { - return strtoul(str.c_str() + 2, nullptr, 16); - } - return strtoul(str.c_str(), nullptr, 16); -} - -class EcXmlParser::Impl { -public: - std::map slaveConfigs; - std::unique_ptr xmlDoc; - - bool loadXmlFile(const std::string& path) { - try { - xmlDoc = std::make_unique(path); - return true; - } catch (const std::exception& e) { - printf("Failed to load XML file: %s, error: %s\n", path.c_str(), e.what()); - return false; - } - } - - void parsePdoEntry(const XmlNode& entryNode, EcPdoEntry& entry) { - if (entryNode.hasChild("Index")) { - XmlNode idxNode = entryNode.getChild("Index"); - entry.index = (uint16_t)parseHexValue(idxNode.getNodeValue()); - } - if (entryNode.hasChild("SubIndex")) { - XmlNode subIdxNode = entryNode.getChild("SubIndex"); - entry.subindex = (uint8_t)atoi(subIdxNode.getNodeValue().c_str()); - } - if (entryNode.hasChild("BitLen")) { - XmlNode bitLenNode = entryNode.getChild("BitLen"); - entry.bitlen = (uint8_t)atoi(bitLenNode.getNodeValue().c_str()); - } - if (entryNode.hasChild("Name")) { - XmlNode nameEntryNode = entryNode.getChild("Name"); - entry.name = nameEntryNode.getNodeValue(); - } - if (entryNode.hasChild("DataType")) { - XmlNode dataTypeNode = entryNode.getChild("DataType"); - entry.dataType = stringToDataType(dataTypeNode.getNodeValue()); - } - } - - void parsePdoMapping(const XmlNode& pdoNode, bool isRxPdo, EcModule& module) { - if (!pdoNode.hasChild("Index")) { - printf("Warning: PDO missing Index node, skipping\n"); - return; - } - - XmlNode indexNode = pdoNode.getChild("Index"); - uint16_t index = (uint16_t)parseHexValue(indexNode.getNodeValue()); - - EcPdo pdo; - pdo.index = index; - pdo.isRxPdo = isRxPdo; - - if (pdoNode.hasChild("Name")) { - XmlNode nameNode = pdoNode.getChild("Name"); - pdo.name = nameNode.getNodeValue(); - } - - std::vector entries = pdoNode.getChildren("Entry"); - for (const auto& entryNode : entries) { - EcPdoEntry entry; - parsePdoEntry(entryNode, entry); - pdo.entries.push_back(entry); - } - - pdo.updateMapCount(); - - if (isRxPdo) { - module.rxPdos.push_back(pdo); - } else { - module.txPdos.push_back(pdo); - } - } - - void parseModule(const XmlNode& moduleNode, EcSlaveConfig& config) { - if (!moduleNode.hasChild("Type")) { - printf("Warning: Module missing Type node, skipping\n"); - return; - } - - auto typeNode = moduleNode.getChild("Type"); - std::string moduleIdentStr = typeNode.getAttrDefault("ModuleIdent", ""); - if (moduleIdentStr.empty()) { - printf("Warning: Module missing ModuleIdent attribute, skipping\n"); - return; - } - - EcModule module; - module.moduleIdent = (uint32_t)parseHexValue(moduleIdentStr); - - if (moduleNode.hasChild("Name")) { - XmlNode nameNode = moduleNode.getChild("Name"); - module.name = nameNode.getNodeValue(); - } - - if (moduleNode.hasChild("RxPdo")) { - auto rxPdoNode = moduleNode.getChild("RxPdo"); - parsePdoMapping(rxPdoNode, true, module); - } - - if (moduleNode.hasChild("TxPdo")) { - auto txPdoNode = moduleNode.getChild("TxPdo"); - parsePdoMapping(txPdoNode, false, module); - } - - config.modules.push_back(module); - - for (const auto& pdo : module.rxPdos) { - config.availableRxPdos.push_back(pdo.index); - } - for (const auto& pdo : module.txPdos) { - config.availableTxPdos.push_back(pdo.index); - } - - printf(" Parsed module: 0x%08X - %s\n", module.moduleIdent, module.name.c_str()); - printf(" RxPDOs: %zu, TxPDOs: %zu\n", module.rxPdos.size(), module.txPdos.size()); - } - - bool parseSlaveConfigById(uint16_t slaveId, EcSlaveConfig& outConfig) { - if (!xmlDoc) return false; - - XmlNode rootNode = xmlDoc->getRoot(); - if (rootNode.getNodeName() != "EtherCATInfo") { - printf("Root node is not EtherCATInfo\n"); - return false; - } - - if (!rootNode.hasChild("Descriptions")) { - printf("No Descriptions node found\n"); - return false; - } - XmlNode descNode = rootNode.getChild("Descriptions"); - - outConfig = EcSlaveConfig(); - outConfig.slaveId = slaveId; - - if (descNode.hasChild("Devices")) { - XmlNode devicesNode = descNode.getChild("Devices"); - if (devicesNode.hasChild("Device")) { - XmlNode deviceNode = devicesNode.getChild("Device"); - if (deviceNode.hasChild("Type")) { - XmlNode typeNode = deviceNode.getChild("Type"); - std::string productCode = typeNode.getAttrDefault("ProductCode", ""); - if (!productCode.empty()) { - outConfig.productCode = (uint32_t)parseHexValue(productCode); - } - std::string revision = typeNode.getAttrDefault("RevisionNo", ""); - if (!revision.empty()) { - outConfig.revisionNo = (uint32_t)atoi(revision.c_str()); - } - } - if (deviceNode.hasChild("Name")) { - XmlNode nameNode = deviceNode.getChild("Name"); - outConfig.name = nameNode.getNodeValue(); - } - } - } - - if (!descNode.hasChild("Modules")) { - printf("Warning: No Modules node found under Descriptions\n"); - return !outConfig.modules.empty(); - } - - XmlNode modulesNode = descNode.getChild("Modules"); - printf("Found Modules node under Descriptions, parsing PDO configurations...\n"); - - std::vector moduleNodes = modulesNode.getChildren("Module"); - if (moduleNodes.empty()) { - printf("Warning: No Module element found under Modules\n"); - return !outConfig.modules.empty(); - } - - for (const auto& moduleNode : moduleNodes) { - parseModule(moduleNode, outConfig); - } - - printf("Total %zu modules parsed\n", moduleNodes.size()); - return true; - } -}; - -// ==================== EcXmlParser 公共接口 ==================== - -EcXmlParser::EcXmlParser() : pImpl(std::make_unique()) {} -EcXmlParser::~EcXmlParser() = default; - -bool EcXmlParser::loadXmlFile(const std::string& xmlPath) { - return pImpl->loadXmlFile(xmlPath); -} - -bool EcXmlParser::parseSlaveConfig(uint16_t slaveId, EcSlaveConfig& outConfig) { - if (!pImpl->parseSlaveConfigById(slaveId, outConfig)) { - return false; - } - pImpl->slaveConfigs[slaveId] = outConfig; - return true; -} - -EcSlaveConfig* EcXmlParser::getSlaveConfig(uint16_t slaveId) { - auto it = pImpl->slaveConfigs.find(slaveId); - return (it != pImpl->slaveConfigs.end()) ? &it->second : nullptr; -} - -const EcSlaveConfig* EcXmlParser::getSlaveConfig(uint16_t slaveId) const { - auto it = pImpl->slaveConfigs.find(slaveId); - return (it != pImpl->slaveConfigs.end()) ? &it->second : nullptr; -} - -std::vector EcXmlParser::getAvailableRxPdos(uint16_t slaveId) const { - auto it = pImpl->slaveConfigs.find(slaveId); - return (it != pImpl->slaveConfigs.end()) ? it->second.availableRxPdos : std::vector(); -} - -std::vector EcXmlParser::getAvailableTxPdos(uint16_t slaveId) const { - auto it = pImpl->slaveConfigs.find(slaveId); - return (it != pImpl->slaveConfigs.end()) ? it->second.availableTxPdos : std::vector(); -} - -const EcPdo* EcXmlParser::findPdo(uint16_t slaveId, uint16_t pdoIndex, bool isRxPdo) const { - auto it = pImpl->slaveConfigs.find(slaveId); - if (it == pImpl->slaveConfigs.end()) return nullptr; - - for (const auto& module : it->second.modules) { - const auto& pdos = isRxPdo ? module.rxPdos : module.txPdos; - for (const auto& pdo : pdos) { - if (pdo.index == pdoIndex) return &pdo; - } - } - return nullptr; -} - -const EcModule* EcXmlParser::findModuleByIndex(uint16_t slaveId, uint16_t pdoIndex, bool isRxPdo) const { - auto it = pImpl->slaveConfigs.find(slaveId); - if (it == pImpl->slaveConfigs.end()) return nullptr; - - for (const auto& module : it->second.modules) { - const auto& pdos = isRxPdo ? module.rxPdos : module.txPdos; - for (const auto& pdo : pdos) { - if (pdo.index == pdoIndex) return &module; - } - } - return nullptr; -} - -const EcModule* EcXmlParser::findModuleByIdent(uint16_t slaveId, uint32_t moduleIdent) const { - auto it = pImpl->slaveConfigs.find(slaveId); - if (it == pImpl->slaveConfigs.end()) return nullptr; - - for (const auto& module : it->second.modules) { - if (module.moduleIdent == moduleIdent) return &module; - } - return nullptr; -} - -void EcXmlParser::printSlaveInfo(uint16_t slaveId) const { - auto it = pImpl->slaveConfigs.find(slaveId); - if (it == pImpl->slaveConfigs.end()) { - printf("Slave %d not configured\n", slaveId); - return; - } - - const EcSlaveConfig& config = it->second; - printf("\n========== Slave %d Configuration ==========\n", slaveId); - printf("Name: %s\n", config.name.c_str()); - printf("Product Code: 0x%08X\n", config.productCode); - printf("Revision: %u\n", config.revisionNo); - - for (const auto& module : config.modules) { - printf("\n[Module 0x%08X] %s\n", module.moduleIdent, module.name.c_str()); - for (const auto& pdo : module.rxPdos) { - printf(" RxPDO 0x%04X - %s (%d entries)\n", pdo.index, pdo.name.c_str(), pdo.mapCount); - for (const auto& entry : pdo.entries) { - printf(" -> 0x%04X:%02X (%d bits) - %s\n", - entry.index, entry.subindex, entry.bitlen, entry.name.c_str()); - } - } - for (const auto& pdo : module.txPdos) { - printf(" TxPDO 0x%04X - %s (%d entries)\n", pdo.index, pdo.name.c_str(), pdo.mapCount); - for (const auto& entry : pdo.entries) { - printf(" <- 0x%04X:%02X (%d bits) - %s\n", - entry.index, entry.subindex, entry.bitlen, entry.name.c_str()); - } - } - } - printf("==========================================\n"); -} - -void EcXmlParser::clear() { - pImpl->slaveConfigs.clear(); -} \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_xml_parser.h b/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_xml_parser.h deleted file mode 100644 index c7b72536..00000000 --- a/cmvr-es/devices/motor/eyou_motor/ethercat/protocol/ec_xml_parser.h +++ /dev/null @@ -1,41 +0,0 @@ -// ec_xml_parser.h -#ifndef EC_XML_PARSER_H -#define EC_XML_PARSER_H - -#include "ec_types.h" -#include -#include -#include - -// XML PDO配置解析器类(仅负责解析XML) -class EcXmlParser { -public: - EcXmlParser(); - ~EcXmlParser(); - - bool loadXmlFile(const std::string& xmlPath); - bool parseSlaveConfig(uint16_t slaveId, EcSlaveConfig& outConfig); - - // 获取已解析的配置 - EcSlaveConfig* getSlaveConfig(uint16_t slaveId); - const EcSlaveConfig* getSlaveConfig(uint16_t slaveId) const; - - // 查询接口 - std::vector getAvailableRxPdos(uint16_t slaveId) const; - std::vector getAvailableTxPdos(uint16_t slaveId) const; - const EcPdo* findPdo(uint16_t slaveId, uint16_t pdoIndex, bool isRxPdo) const; - const EcModule* findModuleByIndex(uint16_t slaveId, uint16_t pdoIndex, bool isRxPdo) const; - const EcModule* findModuleByIdent(uint16_t slaveId, uint32_t moduleIdent) const; - - // 打印信息 - void printSlaveInfo(uint16_t slaveId) const; - - // 清空所有配置 - void clear(); - -private: - class Impl; - std::unique_ptr pImpl; -}; - -#endif // EC_XML_PARSER_H \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/eyou_motor.cpp b/cmvr-es/devices/motor/eyou_motor/eyou_motor.cpp deleted file mode 100644 index d9e2f043..00000000 --- a/cmvr-es/devices/motor/eyou_motor/eyou_motor.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by cmvr on 2026/3/30. -// - -#include "eyou_motor.h" \ No newline at end of file diff --git a/cmvr-es/devices/motor/eyou_motor/eyou_motor.h b/cmvr-es/devices/motor/eyou_motor/eyou_motor.h deleted file mode 100644 index da4d8ae3..00000000 --- a/cmvr-es/devices/motor/eyou_motor/eyou_motor.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// Created by cmvr on 2026/3/30. -// -#pragma once -#include "../abstract_motor.h" -#include "motor/motor_protocol_interface.h" -#include "motor/eyou_motor/ethercat/eyou_motor_ethercat_protocol.h" - -namespace cmvr { - namespace device { - class EYouMotor : public AbstractMotor { - public: - EYouMotor(const XmlNode &config,uint8_t node_id):AbstractMotor(config,node_id){} - EYouMotor(const XmlNode &config):AbstractMotor(config) { - info_.id = config.getAttrDefault("slave", -1); - info_.joint_name = config.getAttrString("jointName"); - info_.limitQ = config.getAttrDefault("limitQ", 3.14f); - info_.limitQd = config.getAttrDefault("limitQd", 0.5f); - node_id_ = info_.id; - } - - EYouMotor(const cmvr::config::EthercatMotorConfig& config) : AbstractMotor() { - info_.id = config.slave(); - info_.joint_name = config.jointname(); - info_.limitQ = config.limitqlb(); - info_.limitQd = config.limitqd(); - node_id_ = info_.id; - } - /** - * 电机初始化在具体的类中完成 - */ - void init() override { - if (!protocol_) { - throw std::runtime_error("Protocol not set for motor"); - } - if (protocol_->comm_proto == MotorProtocolInterface::CommProto::ETHERCAT ) { - auto ethercat_protocol = std::dynamic_pointer_cast(protocol_); - ethercat_protocol->initNode(node_id_); - ethercat_protocol->setQdd(node_id_,1); - } - } - }; - - - } -} diff --git a/cmvr-es/devices/motor/motor_protocol_interface.h b/cmvr-es/devices/motor/motor_protocol_interface.h index 6b49aa06..634a715c 100644 --- a/cmvr-es/devices/motor/motor_protocol_interface.h +++ b/cmvr-es/devices/motor/motor_protocol_interface.h @@ -15,8 +15,7 @@ namespace cmvr { public: enum class CommProto : uint8_t { CANOPEN = 1, - ETHERCAT = 2, - CUSTOM = 3 + CUSTOM = 2 }; virtual ~MotorProtocolInterface() = default; diff --git a/cmvr-es/devices/motor/ti5_motor/ti5_motor.h b/cmvr-es/devices/motor/ti5_motor/ti5_motor.h index 65f44ddc..cfc9bde9 100644 --- a/cmvr-es/devices/motor/ti5_motor/ti5_motor.h +++ b/cmvr-es/devices/motor/ti5_motor/ti5_motor.h @@ -1,12 +1,11 @@ // // Created by lgv on 2025/8/1. // -#pragma once #include "../abstract_motor.h" #include "motor/motor_protocol_interface.h" #include "motor/ti5_motor/canopen/ti5_motor_canopen_protocol.h" -#include "cmvr/config/robot_config/robot_config.pb.h" + /** * class: Ti5Motor 完成电机硬件的相关工作,软件相关的在protocol 中完成 */ @@ -23,15 +22,6 @@ namespace cmvr { node_id_ = info_.id; } - Ti5Motor(const cmvr::config::MotorConfig& config) : AbstractMotor() { - info_.id = config.id(); - info_.joint_name = config.jointname(); - info_.limitQ = config.limitqlb(); - info_.limitQd = config.limitqd(); - node_id_ = info_.id; - } - - /** * 电机初始化在具体的类中完成 */ diff --git a/cmvr-es/devices/robot/abstract_robot.h b/cmvr-es/devices/robot/abstract_robot.h index 91daf0ae..5759c3af 100644 --- a/cmvr-es/devices/robot/abstract_robot.h +++ b/cmvr-es/devices/robot/abstract_robot.h @@ -41,7 +41,6 @@ namespace cmvr::device{ class AbstractRobot: public AbstractDevice { public: - AbstractRobot() = default; explicit AbstractRobot(const XmlNode &config): AbstractDevice(config) {} ~AbstractRobot() override=default; diff --git a/cmvr-es/devices/robot/humanoid_robot/include/humanoid_robot.h b/cmvr-es/devices/robot/humanoid_robot/include/humanoid_robot.h index 9cd2d11d..7770997b 100644 --- a/cmvr-es/devices/robot/humanoid_robot/include/humanoid_robot.h +++ b/cmvr-es/devices/robot/humanoid_robot/include/humanoid_robot.h @@ -35,8 +35,6 @@ #include "planner/joint_space_planner/include/joint_space_planner_creator.h" #include "planner/joint_space_planner/include/joint_space_planner.h" #include "ik_solver/include/pinocchio_dls_ik_solver.h" -#include "cmvr/config/robot_config/robot_config.pb.h" - namespace cmvr::device{ @@ -51,10 +49,12 @@ namespace cmvr::device{ ROBOT_ERROR } RobotStateMachine; +// typedef enum { +// PPM_MODE, PVM_MODE, PTM_MODE, CSP_MODE, CSV_MODE, CSC_MODE +// } RobotRunningMode; + public: explicit HumanoidRobot(const XmlNode& cfg); - HumanoidRobot(const cmvr::config::HumanRobotConfig& config); - HumanoidRobot(const cmvr::config::EthercatRobotConfig& config); ~HumanoidRobot(); void init() override; @@ -232,7 +232,6 @@ namespace cmvr::device{ std::atomic is_head_busy_{false}, is_waist_busy_{false}; - std::string urdf_path_ = ""; }; diff --git a/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot.cpp b/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot.cpp index 9cc52666..0146c053 100644 --- a/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot.cpp +++ b/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot.cpp @@ -49,87 +49,6 @@ HumanoidRobot::HumanoidRobot(const XmlNode &cfg) : AbstractRobot(cfg) { } } - -template -HumanoidRobot::HumanoidRobot(const cmvr::config::HumanRobotConfig& config) { - try { - id_ = config.id(); - urdf_path_ = config.urdf(); - dof_ = DOF; - if (!pathExists(config.urdf())) { - throw runtime_error("urdf file does not exist"); - } - auto rcfg = cmvr::dyn::LoadRobotFromURDF( - config.urdf(), config.baselink()); - m_robot_ = std::make_shared >(rcfg); - - // 解析jointNames字符串,按逗号分隔 - joint_names_ = splitString(config.jointnames(), ","); - // 解析linkNames字符串,按逗号分隔 - link_names_ = splitString(config.linknames(), ","); - - if (joint_names_.size() != dof_) { - throw runtime_error("joint names size mismatched with dof"); - } - - m_state_ = m_robot_->MakeState(link_names_, joint_names_); - m_cctrl_ = make_shared >(m_robot_); - upd_freq_ = config.updfreq(); - CSP_buffer_ = make_shared >(config.buffersize()); - CSV_buffer_ = make_shared >(config.buffersize()); - CSC_buffer_ = make_shared >(config.buffersize()); - - upd_freq_ = 1000; - upd_timer_ = make_shared(); - upd_timer_->start(chrono::nanoseconds(1000 / upd_freq_ * 1000), - [this] { update_state_(); }); - rsm_.store(ROBOT_READY); - } catch (exception &e) { - LOG(ERROR) << "HumanoidRobot init failed, id=" << id_; - throw runtime_error(e.what()); - } -} - -template -HumanoidRobot::HumanoidRobot(const cmvr::config::EthercatRobotConfig& config) { - try { - id_ = config.id(); - urdf_path_ = config.urdf(); - dof_ = DOF; - if (!pathExists(config.urdf())) { - throw runtime_error("urdf file does not exist"); - } - auto rcfg = cmvr::dyn::LoadRobotFromURDF( - config.urdf(), config.baselink()); - m_robot_ = std::make_shared >(rcfg); - - // 解析jointNames字符串,按逗号分隔 - joint_names_ = splitString(config.jointnames(), ","); - // 解析linkNames字符串,按逗号分隔 - link_names_ = splitString(config.linknames(), ","); - - if (joint_names_.size() != dof_) { - throw runtime_error("joint names size mismatched with dof"); - } - - m_state_ = m_robot_->MakeState(link_names_, joint_names_); - m_cctrl_ = make_shared >(m_robot_); - upd_freq_ = config.updfreq(); - CSP_buffer_ = make_shared >(config.buffersize()); - CSV_buffer_ = make_shared >(config.buffersize()); - CSC_buffer_ = make_shared >(config.buffersize()); - - upd_freq_ = 1000; - upd_timer_ = make_shared(); - upd_timer_->start(chrono::nanoseconds(1000 / upd_freq_ * 1000), - [this] { update_state_(); }); - rsm_.store(ROBOT_READY); - } catch (exception &e) { - LOG(ERROR) << "HumanoidRobot init failed, id=" << id_; - throw runtime_error(e.what()); - } -} - template void HumanoidRobot::init() { motor_manager_ = MotorsInfo::getInstance()->getMotorManager(); @@ -140,7 +59,7 @@ void HumanoidRobot::init() { std::vector(7, 1.0)); - ik_solver_ = std::make_shared("/home/cmvr/Projects/cmvr-es/model/xiaoyan_description/dual_arm.urdf", + ik_solver_ = std::make_shared("/home/lgv/cmvr/cmvr-es/model/xiaoyan_description/dual_arm.urdf", "PELVIS_S", "R_WRIST_R_S", "R_FINGER_TIP_FIXED"); diff --git a/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot_test.cpp b/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot_test.cpp index ed16d61d..d1b47e4e 100644 --- a/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot_test.cpp +++ b/cmvr-es/devices/robot/humanoid_robot/src/humanoid_robot_test.cpp @@ -990,21 +990,3 @@ TEST(HumanoidRobotTest,IBVSWithRealRobotTrackedPoint) { EXPECT_GT(ok_steps, 0) << "No successful tracked-point IBVS control steps."; } - - -TEST(HumanoidRobotTest, EYouTest) { - auto& dmgr = DeviceManager::getInstance(); - auto robot_abs = dmgr.getDevice("ec_robot_01"); - auto robot = std::dynamic_pointer_cast>(robot_abs); - ASSERT_NE(robot, nullptr) << "hc01 is not HumanoidRobot<14>"; - - std::vector init_cmd = { - {"L_SHOULDER_P", 0.1} - }; - // robot->moveJ(init_cmd, 0.1, 1.0); - - // robot->calibrateZeroQ("L_SHOULDER_P"); - - robot->torqueOff("L_SHOULDER_P"); - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); -} diff --git a/cmvr-es/main.cpp b/cmvr-es/main.cpp index c5531829..ab0f76d3 100644 --- a/cmvr-es/main.cpp +++ b/cmvr-es/main.cpp @@ -9,7 +9,6 @@ #include #include "service/grpc/include/server_runner.h" #include "utils/base/include/logger.h" - using namespace cmvr::service; int main(int argc, char* argv[]) { std::string config_path; diff --git a/cmvr-es/monitor/abstract_monitor.h b/cmvr-es/monitor/abstract_monitor.h index 244e5722..6b707e9c 100644 --- a/cmvr-es/monitor/abstract_monitor.h +++ b/cmvr-es/monitor/abstract_monitor.h @@ -13,7 +13,6 @@ namespace cmvr::monitor{ class AbstractMonitor { public: - AbstractMonitor() = default; explicit AbstractMonitor(const XmlNode &cfg){ cfg_ = cfg; freq_ = cfg_.getAttrDefault("freq", 1); diff --git a/cmvr-es/monitor/diskmonitor/include/disk_monitor.h b/cmvr-es/monitor/diskmonitor/include/disk_monitor.h index a22256b3..89664362 100644 --- a/cmvr-es/monitor/diskmonitor/include/disk_monitor.h +++ b/cmvr-es/monitor/diskmonitor/include/disk_monitor.h @@ -8,21 +8,17 @@ #include #include "../../abstract_monitor.h" -#include "cmvr/config/monitor_config/monitor_config.pb.h" namespace cmvr::monitor { class DiskMonitor final : public AbstractMonitor{ public: explicit DiskMonitor(const XmlNode& config); - explicit DiskMonitor(const cmvr::config::DiskMonitorConfig& config); // 新增proto构造函数 ~DiskMonitor(); protected: bool check_() override; bool execution_() override; private: - void initFromProto(const cmvr::config::DiskMonitorConfig& config); // 新增初始化方法 - std::unordered_map folders_; std::vector dirsToClean_; // 需要清理的目录列表 mutable std::mutex mutex_; // 保护共享资源的互斥锁 @@ -31,4 +27,6 @@ namespace cmvr::monitor { }; } + + #endif //FOLDER_MONITOR_H \ No newline at end of file diff --git a/cmvr-es/monitor/diskmonitor/src/disk_monitor.cpp b/cmvr-es/monitor/diskmonitor/src/disk_monitor.cpp index 9f4497cf..20630615 100644 --- a/cmvr-es/monitor/diskmonitor/src/disk_monitor.cpp +++ b/cmvr-es/monitor/diskmonitor/src/disk_monitor.cpp @@ -14,6 +14,7 @@ using namespace cmvr::monitor; namespace fs = std::filesystem; using namespace std::chrono; + // 文件信息结构体 struct FileInfo { std::string path; @@ -37,40 +38,51 @@ bool deleteOldestFile(const std::string& directoryPath) { struct dirent* entry; struct stat fileStat; + // 遍历目录中的每个条目 while ((entry = readdir(dir)) != nullptr) { + // 跳过 "." 和 ".." if (std::string(entry->d_name) == "." || std::string(entry->d_name) == "..") { continue; } + // 构建完整路径 std::string filePath = directoryPath + "/" + entry->d_name; + // 获取文件状态信息 if (stat(filePath.c_str(), &fileStat) == -1) { std::cerr << "Failed to get file status: " << filePath << std::endl; continue; } + // 只处理普通文件 if (S_ISREG(fileStat.st_mode)) { FileInfo fileInfo; fileInfo.path = filePath; + + // 优先使用 st_birthtime (如果可用),否则使用 st_ctime #ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME fileInfo.creationTime = fileStat.st_birthtime; #else fileInfo.creationTime = fileStat.st_ctime; #endif + files.push_back(fileInfo); } } closedir(dir); + // 如果没有找到文件,返回失败 if (files.empty()) { std::cout << "No files found in directory: " << directoryPath << std::endl; return false; } + // 按创建时间排序 std::sort(files.begin(), files.end(), compareByCreationTime); - std::string oldestFilePath = files[0].path; + // 删除最早创建的文件 + std::string oldestFilePath = files[0].path; if (unlink(oldestFilePath.c_str()) == -1) { std::cerr << "Failed to delete file: " << oldestFilePath << std::endl; return false; @@ -80,11 +92,11 @@ bool deleteOldestFile(const std::string& directoryPath) { return true; } + bool pathExists(const std::string& path) { struct stat buffer; return (stat(path.c_str(), &buffer) == 0); } - int countFilesInDirectory(const std::string& path) { DIR* dir = opendir(path.c_str()); if (!dir) { @@ -95,7 +107,9 @@ int countFilesInDirectory(const std::string& path) { int count = 0; struct dirent* entry; + // 遍历目录中的每个条目 while ((entry = readdir(dir)) != nullptr) { + // 跳过 "." 和 ".." if (std::string(entry->d_name) != "." && std::string(entry->d_name) != "..") { count++; } @@ -105,8 +119,8 @@ int countFilesInDirectory(const std::string& path) { return count; } -// XML构造函数 DiskMonitor::DiskMonitor(const XmlNode& config) : AbstractMonitor(config){ + try { for (auto &node: config.getChildren()){ string fileDir = node.getAttrString("fileDir"); @@ -124,42 +138,13 @@ DiskMonitor::DiskMonitor(const XmlNode& config) : AbstractMonitor(config){ throw runtime_error("[DiskMonitor] (DiskMonitor): Failed "+ string(e.what())); } } - -// Proto构造函数 -DiskMonitor::DiskMonitor(const cmvr::config::DiskMonitorConfig& config) : AbstractMonitor() { - try { - initFromProto(config); - is_initialized_ = true; - LOG(INFO) << "[DiskMonitor]: Init DiskMonitor from proto Success"; - } - catch (const exception& e) { - LOG(ERROR) << "[DiskMonitor] (DiskMonitor): Failed " << e.what(); - throw runtime_error("[DiskMonitor] (DiskMonitor): Failed " + string(e.what())); - } -} - -// Proto初始化方法 -void DiskMonitor::initFromProto(const cmvr::config::DiskMonitorConfig& config) { - // 设置监控ID和频率(通过AbstractMonitor) - // 假设AbstractMonitor有setId和setFreq方法,如果没有需要添加 - // 这里根据你的AbstractMonitor实现来调整 - - for (const auto& folder : config.folders()) { - string fileDir = folder.filedir(); - if (!pathExists(fileDir)) { - throw runtime_error("[DiskMonitor] (initFromProto): path not exists: " + fileDir); - } - int maxVolume = folder.maxvolume(); - folders_[fileDir] = maxVolume; - } -} - DiskMonitor::~DiskMonitor() { folders_.clear(); } - bool DiskMonitor::check_() { + // 检查当前目录下的文件数量是否超过最大值 bool allOk = true; + // 遍历所有监控的文件夹 for (const auto& folder : folders_) { const std::string& path = folder.first; size_t maxFiles = folder.second; @@ -169,21 +154,28 @@ bool DiskMonitor::check_() { continue; } int nFileCount = countFilesInDirectory(path); - if (nFileCount > maxFiles) { + if (nFileCount > maxFiles) + { allOk = false; dirsToClean_.push_back(path); std::cout << "path need remove oldest file: " << path << std::endl; } + + } catch (const fs::filesystem_error& e) { std::cerr << "Filesystem error: " << e.what() << '\n'; } catch (const std::exception& e) { std::cerr << "Unexpected error: " << e.what() << '\n'; } } + return allOk; } + bool DiskMonitor::execution_() { + + // 删除创建时间最早的文件 std::lock_guard lock(mutex_); bool success = true; @@ -191,6 +183,6 @@ bool DiskMonitor::execution_() { deleteOldestFile(path); } - dirsToClean_.clear(); + dirsToClean_.clear(); // 清理完成后清空列表 return success; } \ No newline at end of file diff --git a/cmvr-es/monitor_manager/include/monitor_factory.h b/cmvr-es/monitor_manager/include/monitor_factory.h index cdd879a3..a0dbc900 100644 --- a/cmvr-es/monitor_manager/include/monitor_factory.h +++ b/cmvr-es/monitor_manager/include/monitor_factory.h @@ -5,8 +5,8 @@ #ifndef MONITOR_FACTORY_H #define MONITOR_FACTORY_H -#include "monitor/abstract_monitor.h" -#include "cmvr/config/monitor_config/monitor_config.pb.h" + +#include "../../monitor/abstract_monitor.h" namespace cmvr::monitor { @@ -14,17 +14,14 @@ namespace cmvr::monitor { public: MonitorFactory() = default; - - // 原有的XML方式 template std::shared_ptr create(const XmlNode& cfg); std::shared_ptr create(const XmlNode& cfg); - - // 新增proto方式 - template - std::shared_ptr create(const cmvr::config::DiskMonitorConfig& config); - std::shared_ptr create(const cmvr::config::DiskMonitorConfig& config); }; + + } + + #endif //MONITOR_FACTORY_H \ No newline at end of file diff --git a/cmvr-es/monitor_manager/include/monitor_manager.h b/cmvr-es/monitor_manager/include/monitor_manager.h index 422ece1f..8c53a401 100644 --- a/cmvr-es/monitor_manager/include/monitor_manager.h +++ b/cmvr-es/monitor_manager/include/monitor_manager.h @@ -15,38 +15,23 @@ namespace cmvr::monitor { public: MonitorManager(const MonitorManager&) = delete; MonitorManager& operator=(const MonitorManager&) = delete; - - // 原有的XML方式 static MonitorManager& getInstance(const XmlNode &cfg); - - // 新增proto方式 - static MonitorManager& getInstance(const cmvr::config::MonitorManagerConfig& config); - static MonitorManager& getInstance(); static void destroyInstance(); private: - // 原有的XML方式 - explicit MonitorManager(const XmlNode &cfg); - - // 新增proto方式 - explicit MonitorManager(const cmvr::config::MonitorManagerConfig& config); - - void init_monitors(); // XML方式 - void init_monitors_from_proto(); // Proto方式 - + explicit MonitorManager (const XmlNode &cfg); + void init_monitors(); private: static std::once_flag init_flag_; static std::shared_ptr instance_; - // 两种配置方式,只有一个有效 - bool use_proto_ = false; - XmlNode cfg_; // XML配置 - cmvr::config::MonitorManagerConfig proto_cfg_; // Proto配置 - + XmlNode cfg_; std::shared_ptr monitor_factory_; std::unordered_map> monitors_; }; } + + #endif //MONITOR_MANAGER_H \ No newline at end of file diff --git a/cmvr-es/monitor_manager/src/monitor_factory.cpp b/cmvr-es/monitor_manager/src/monitor_factory.cpp index e770f294..821373e2 100644 --- a/cmvr-es/monitor_manager/src/monitor_factory.cpp +++ b/cmvr-es/monitor_manager/src/monitor_factory.cpp @@ -3,12 +3,12 @@ // #include "../include/monitor_factory.h" + #include "../../monitor/diskmonitor/include/disk_monitor.h" using namespace std; using namespace cmvr::monitor; -// XML方式的模板特化 template std::shared_ptr MonitorFactory::create(const XmlNode&); template @@ -17,30 +17,8 @@ std::shared_ptr MonitorFactory::create(const XmlNode& cfg) { return std::make_shared(cfg); } else { - LOG(ERROR) << "[MonitorFactory]: Unsupported monitor type"; - throw runtime_error("[MonitorFactory]: Unsupported monitor type"); + LOG(ERROR) << "[DeviceFactory]: Unsupported device type"; + throw runtime_error("[DeviceFactory]: Unsupported device type"); } } -// Proto方式的模板特化 -template std::shared_ptr MonitorFactory::create(const cmvr::config::DiskMonitorConfig&); - -template -std::shared_ptr MonitorFactory::create(const cmvr::config::DiskMonitorConfig& config) { - if constexpr (std::is_same_v) { - return std::make_shared(config); - } - else { - LOG(ERROR) << "[MonitorFactory]: Unsupported monitor type"; - throw runtime_error("[MonitorFactory]: Unsupported monitor type"); - } -} - -std::shared_ptr MonitorFactory::create(const XmlNode& cfg) { - // 保留原有实现 - return nullptr; -} - -std::shared_ptr MonitorFactory::create(const cmvr::config::DiskMonitorConfig& config) { - return std::make_shared(config); -} \ No newline at end of file diff --git a/cmvr-es/monitor_manager/src/monitor_manager.cpp b/cmvr-es/monitor_manager/src/monitor_manager.cpp index dde9b386..2dd9e2cf 100644 --- a/cmvr-es/monitor_manager/src/monitor_manager.cpp +++ b/cmvr-es/monitor_manager/src/monitor_manager.cpp @@ -3,7 +3,8 @@ // #include "../include/monitor_manager.h" -#include "monitor/diskmonitor/include/disk_monitor.h" + +#include "../../monitor/diskmonitor/include/disk_monitor.h" using namespace std; using namespace cmvr::monitor; @@ -11,19 +12,11 @@ using namespace cmvr::monitor; std::shared_ptr MonitorManager::instance_ = nullptr; std::once_flag MonitorManager::init_flag_; -// XML方式构造函数 -MonitorManager::MonitorManager(const XmlNode& cfg) : use_proto_(false), cfg_(cfg) { +MonitorManager::MonitorManager(const XmlNode& cfg) { monitor_factory_ = make_shared(); + cfg_ = cfg; init_monitors(); } - -// Proto方式构造函数 -MonitorManager::MonitorManager(const cmvr::config::MonitorManagerConfig& config) : use_proto_(true), proto_cfg_(config) { - monitor_factory_ = make_shared(); - init_monitors_from_proto(); -} - -// XML方式单例 MonitorManager& MonitorManager::getInstance(const XmlNode& cfg) { std::call_once(init_flag_, [&cfg] { instance_.reset(new MonitorManager(cfg)); @@ -31,17 +24,9 @@ MonitorManager& MonitorManager::getInstance(const XmlNode& cfg) { return *instance_; } -// Proto方式单例 -MonitorManager& MonitorManager::getInstance(const cmvr::config::MonitorManagerConfig& config) { - std::call_once(init_flag_, [&config] { - instance_.reset(new MonitorManager(config)); - }); - return *instance_; -} - MonitorManager& MonitorManager::getInstance() { if (!instance_) { - throw std::runtime_error("[MonitorManager] (getInstance): MonitorManager not initialized. Call getInstance() first."); + throw std::runtime_error("[MonitorManager] (getInstance): MonitorManager not initialized. Call getInstance(const XmlNode&) first."); } return *instance_; } @@ -50,14 +35,13 @@ void MonitorManager::destroyInstance() { instance_.reset(); } -// XML方式初始化 void MonitorManager::init_monitors() { - try { + try{ auto diskmonitor_node = cfg_.getChild("DiskMonitor"); auto id = diskmonitor_node.getAttrString("id"); auto monitor = monitor_factory_->create(diskmonitor_node); if (monitor == nullptr) { - throw std::runtime_error("[MonitorManager]: Factory returned nullptr for monitor ID: " + id); + throw std::runtime_error("[DeviceManager]: Factory returned nullptr for device ID: " + id); } monitors_[id] = monitor; } @@ -65,31 +49,5 @@ void MonitorManager::init_monitors() { LOG(ERROR) << e.what(); throw runtime_error(e.what()); } -} -// Proto方式初始化 -void MonitorManager::init_monitors_from_proto() { - try { - if (!proto_cfg_.has_disk_monitor()) { - LOG(WARNING) << "[MonitorManager]: No disk_monitor config found"; - return; - } - - const auto& disk_monitor_cfg = proto_cfg_.disk_monitor(); - if (!disk_monitor_cfg.enable()) { - LOG(INFO) << "[MonitorManager]: Disk monitor is disabled"; - return; - } - - auto id = disk_monitor_cfg.id(); - auto monitor = monitor_factory_->create(disk_monitor_cfg); - if (monitor == nullptr) { - throw std::runtime_error("[MonitorManager]: Factory returned nullptr for monitor ID: " + id); - } - monitors_[id] = monitor; - } - catch (const exception& e) { - LOG(ERROR) << e.what(); - throw runtime_error(e.what()); - } } \ No newline at end of file diff --git a/cmvr-es/service/CMakeLists.txt b/cmvr-es/service/CMakeLists.txt index 98ea4199..4ef50063 100644 --- a/cmvr-es/service/CMakeLists.txt +++ b/cmvr-es/service/CMakeLists.txt @@ -9,7 +9,6 @@ add_library(service grpc/src/grpc_humanoid_robot_service.cpp grpc/src/grpc_hlc_service.cpp grpc/src/server_runner.cpp - grpc/src/grpc_agv_service.cpp ) target_include_directories(service PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/cmvr-es/service/grpc/include/grpc_agv_service.h b/cmvr-es/service/grpc/include/grpc_agv_service.h deleted file mode 100644 index 02253dda..00000000 --- a/cmvr-es/service/grpc/include/grpc_agv_service.h +++ /dev/null @@ -1,278 +0,0 @@ -// -// Created by xtkuang on 2025/6/1. -// -/** - * @file grpc_agv_service.h - * @brief gRPC AGV 服务实现类的头文件。 - * 继承自自动生成的 api::AgvService::Service,提供所有 AGV 相关 RPC 接口的具体实现。 - * 通过 DeviceManager 获取对应的 AGV 设备实例(如 AgvSrc1100),将请求转发至设备层。 - * @author xtkuang - * @date 2025-06-01 - */ - -#ifndef GRPC_AGV_SERVICE_H -#define GRPC_AGV_SERVICE_H - -#include "cmvr/api/agv_service.grpc.pb.h" -#include "utils/base/include/grpc_utils.h" -#include "device_manager/include/device_manager.h" - -namespace cmvr::service { - -/** - * @brief gRPC AGV 服务实现类。 - * 负责将 gRPC 请求转换为对具体 AGV 设备的调用,并统一处理异常、日志和时间戳。 - */ -class gRPCAGVServiceImpl final : public api::AgvService::Service { -public: - /** - * @brief 构造函数,获取 DeviceManager 单例引用。 - */ - gRPCAGVServiceImpl(); - - /** - * @brief 默认析构函数。 - */ - ~gRPCAGVServiceImpl() override = default; - - // ===================== 基本状态查询接口 ===================== - - /** - * @brief 获取 AGV 基本信息(命令码 1000)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID(header.device_id)。 - * @param response 响应消息,填充 AGV 状态信息(版本、型号、IP、MAC 等)。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status GetStatusInfo(grpc::ServerContext* context, - const api::GetAgvStatusInfoCommand_Request* request, - api::GetAgvStatusInfoCommand_Feedback* response) override; - - /** - * @brief 查询电池状态(命令码 1007)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID 和可选的 simple 标志。 - * @param response 响应消息,填充电池电量、温度、充放电状态等。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status GetBatteryStatus(grpc::ServerContext* context, - const api::RobotStatusBatteryCommand_Request* request, - api::RobotStatusBatteryCommand_Feedback* response) override; - - /** - * @brief 查询机器人当前位置(命令码 1004)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID。 - * @param response 响应消息,填充坐标、朝向角、置信度、当前站点等。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status GetRobotLocation(grpc::ServerContext* context, - const api::RobotStatusLocCommand_Request* request, - api::RobotStatusLocCommand_Feedback* response) override; - - // ===================== 地图管理接口 ===================== - - /** - * @brief 下载指定地图(命令码 4011)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID 和地图名称。 - * @param response 响应消息,返回地图 JSON 内容或错误码。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status RobotConfigDownloadMap(grpc::ServerContext* context, - const api::RobotConfigDownloadMapCommand_Request* request, - api::RobotConfigDownloadMapCommand_Feedback* response) override; - - /** - * @brief 查询地图状态(命令码 1300)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID。 - * @param response 响应消息,返回当前地图名称、所有地图列表及文件详情。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status GetMapStatus(grpc::ServerContext* context, - const api::RobotStatusMapCommand_Request* request, - api::RobotStatusMapCommand_Feedback* response) override; - - /** - * @brief 上传地图(命令码 4010)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID 和完整的地图 JSON 字符串。 - * @param response 响应消息,返回上传结果(ret_code)。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status RobotConfigUploadMap(grpc::ServerContext* context, - const api::RobotConfigUploadMapCommand_Request* request, - api::RobotConfigUploadMapCommand_Feedback* response) override; - - // ===================== 控制权管理接口 ===================== - - /** - * @brief 抢占机器人控制权(命令码 4005)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID 和抢占者昵称。 - * @param response 响应消息,返回抢占结果(ret_code)。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status RobotConfigLock(grpc::ServerContext* context, - const api::RobotConfigLockCommand_Request* request, - api::RobotConfigLockCommand_Feedback* response) override; - - /** - * @brief 查询当前控制权持有者(命令码 1060)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID。 - * @param response 响应消息,返回是否被锁定、持有者 IP/端口/昵称等信息。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status GetCurrentLockStatus(grpc::ServerContext* context, - const api::RobotStatusCurrentLockCommand_Request* request, - api::RobotStatusCurrentLockCommand_Feedback* response) override; - - // ===================== 运动控制接口 ===================== - - /** - * @brief 下发开环速度运动指令(命令码 2010)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID 和速度参数(vx, vy, w, steer, duration 等)。 - * @param response 响应消息,返回指令下发结果(ret_code)。 - * @note 此指令会强制取消当前自动导航任务,多舵轮设备仅 vx/vy/w 生效。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status RobotMotionControl(grpc::ServerContext* context, - const api::RobotMotionControlCommand_Request* request, - api::RobotMotionControlCommand_Feedback* response) override; - - /** - * @brief 切换载入地图(命令码 2022)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID 和目标地图名称。 - * @param response 响应消息,返回切换结果(ret_code)。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status RobotLoadMap(grpc::ServerContext* context, - const api::RobotLoadMapCommand_Request* request, - api::RobotLoadMapCommand_Feedback* response) override; - - /** - * @brief 停止开环运动(命令码 2000)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID(无业务数据)。 - * @param response 响应消息,返回停止结果(ret_code)。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status RobotControlStop(grpc::ServerContext* context, - const api::RobotControlStopCommand::Request* request, - api::RobotControlStopCommand::Feedback* response) override; - - // ===================== 导航任务接口 ===================== - - /** - * @brief 查询地图载入状态(命令码 1022)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID。 - * @param response 响应消息,返回 loadmap_status(0=失败, 1=成功, 2=载入中)。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status QueryLoadMapStatus(grpc::ServerContext* context, - const api::RobotQueryLoadMapStatusCommand_Request* request, - api::RobotQueryLoadMapStatusCommand_Feedback* response) override; - - /** - * @brief 查询当前地图所有站点信息(命令码 1301)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID。 - * @param response 响应消息,返回站点列表(ID、坐标、类型等)。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status QueryStationList(grpc::ServerContext* context, - const api::QueryStationListCommand_Request* request, - api::QueryStationListCommand_Feedback* response) override; - - /** - * @brief 指定路径导航(命令码 3066)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID 和多段导航任务列表(move_task_list)。 - * @param response 响应消息,返回下发结果(ret_code=0 仅表示接收成功,不表示执行完成)。 - * @attention 每个任务必须包含 task_id, source_id, id,且相邻站点间必须有直接路径。 - * 任务会排队执行,适合多车调度场景。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status RobotGoTargetList(grpc::ServerContext* context, - const api::RobotGoTargetListCommand_Request* request, - api::RobotGoTargetListCommand_Feedback* response) override; - - /** - * @brief 查询当前实时导航状态(命令码 1020)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID 和 simple 标志。 - * @param response 响应消息,返回任务状态、任务类型、目标、路径等信息。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status RobotStatusTaskCurrent(grpc::ServerContext* context, - const api::RobotStatusTaskCurrentCommand_Request* request, - api::RobotStatusTaskCurrentCommand_Feedback* response) override; - - /** - * @brief 批量查询任务状态(命令码 1110)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID 和 task_ids 列表(为空则查询所有未完成+最近完成)。 - * @param response 响应消息,返回任务状态包(进度、距离、各任务状态等)。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status RobotStatusTaskPackage(grpc::ServerContext* context, - const api::RobotStatusTaskPackageCommand_Request* request, - api::RobotStatusTaskPackageCommand_Feedback* response) override; - - /** - * @brief 单点站点自动规划导航(命令码 3051)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID 和完整的导航参数(支持自由导航、动作、PGV 等)。 - * @param response 响应消息,返回下发结果(ret_code=0 仅表示接收成功)。 - * @warning 严禁用于多车调度场景,仅限单车测试;新任务会取消当前任务(不排队)。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status RobotGoTarget(grpc::ServerContext* context, - const api::RobotGoTargetCommand_Request* request, - api::RobotGoTargetCommand_Feedback* response) override; - - /** - * @brief 暂停当前导航任务(命令码 3001)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID(无业务数据)。 - * @param response 响应消息,返回暂停结果(ret_code)。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status RobotTaskPause(grpc::ServerContext* context, - const api::RobotTaskPauseCommand::Request* request, - api::RobotTaskPauseCommand::Feedback* response) override; - - /** - * @brief 继续当前导航任务(命令码 3002)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID(无业务数据)。 - * @param response 响应消息,返回继续结果(ret_code)。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status RobotTaskResume(grpc::ServerContext* context, - const api::RobotTaskResumeCommand::Request* request, - api::RobotTaskResumeCommand::Feedback* response) override; - - /** - * @brief 取消当前导航任务(命令码 3003)。 - * @param context gRPC 上下文(未使用)。 - * @param request 请求消息,包含设备 ID(无业务数据)。 - * @param response 响应消息,返回取消结果(ret_code)。 - * @return 始终返回 grpc::Status::OK,错误信息通过 response.header 返回。 - */ - grpc::Status RobotTaskCancel(grpc::ServerContext* context, - const api::RobotTaskCancelCommand::Request* request, - api::RobotTaskCancelCommand::Feedback* response) override; - -private: - device::DeviceManager& dmgr_; ///< 设备管理器引用,用于根据 device_id 获取 AGV 设备实例。 -}; - -} // namespace cmvr::service - -#endif // GRPC_AGV_SERVICE_H \ No newline at end of file diff --git a/cmvr-es/service/grpc/include/server_runner.h b/cmvr-es/service/grpc/include/server_runner.h index 7ad0706e..be8eadf8 100644 --- a/cmvr-es/service/grpc/include/server_runner.h +++ b/cmvr-es/service/grpc/include/server_runner.h @@ -11,7 +11,6 @@ #include #include #include "rapidxml/xml_parser.h" -#include "cmvr/config/server_config/server_config.pb.h" namespace cmvr::service { @@ -42,7 +41,6 @@ namespace cmvr::service { std::thread worker_; XmlNode cfg_; - cmvr::config::ServerConfig servercfg_; std::unique_ptr server_; std::string address_; diff --git a/cmvr-es/service/grpc/src/grpc_agv_service.cpp b/cmvr-es/service/grpc/src/grpc_agv_service.cpp deleted file mode 100644 index a48e03b2..00000000 --- a/cmvr-es/service/grpc/src/grpc_agv_service.cpp +++ /dev/null @@ -1,1208 +0,0 @@ -// -// Created by xtkuang on 2025/6/1. -// -/** - * @file grpc_agv_service.cpp - * @brief gRPC AGV 服务实现。 - * 每个 RPC 方法从请求头中提取 device_id,通过 DeviceManager 获取对应的 AGV 设备, - * 调用设备层的具体方法,并将结果转换为 Protobuf 响应。 - * 统一处理异常,设置响应头中的 success/error_message 和时间戳。 - * @author xtkuang - * @date 2025-06-01 - */ - -#include "./service/grpc/include/grpc_agv_service.h" - -#include - -#include "devices/agv/agv_src1100/include/agv_src1100.h" - -#include "devices/agv/abstract_agv.h" - -namespace cmvr::service { - - /** - * @brief 构造函数,获取 DeviceManager 单例。 - */ - gRPCAGVServiceImpl::gRPCAGVServiceImpl() : dmgr_(device::DeviceManager::getInstance()) { - } - - // ===================== GetStatusInfo ===================== - /** - * @brief 获取 AGV 基本信息。 - * @details 从请求中提取 device_id,调用设备层的 getStatusInfo,填充响应。 - * @param context 未使用。 - * @param request 包含设备 ID。 - * @param response 返回状态信息和响应头。 - * @return 始终返回 OK,错误通过 response.header 传递。 - */ - grpc::Status gRPCAGVServiceImpl::GetStatusInfo( - grpc::ServerContext* context, - const api::GetAgvStatusInfoCommand_Request* request, - api::GetAgvStatusInfoCommand_Feedback* response) - { - try { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (GetStatusInfo): id=" << dev_id; - const auto agv_device = dmgr_.getDevice(dev_id); - - device::AbstractAgv::AgvStatusInfo device_status; - agv_device->getStatusInfo(device_status); - - // 映射到 Protobuf 消息 - auto* proto_status = response->mutable_status(); - proto_status->set_id(device_status.id); - proto_status->set_vehicle_id(device_status.vehicle_id); - proto_status->set_version(device_status.version); - proto_status->set_model(device_status.model); - proto_status->set_dsp_version(device_status.dsp_version); - proto_status->set_current_ip(device_status.current_ip); - proto_status->set_mac(device_status.mac); - proto_status->set_rssi(device_status.rssi); - proto_status->set_ret_code(device_status.ret_code); - proto_status->set_err_msg(device_status.err_msg); - - auto* header = response->mutable_header(); - header->set_success(true); - header->set_error_message(""); - setCurrentTimestamp(header->mutable_timestamp()); - - LOG(INFO) << "GetStatusInfo device_id:" << dev_id << " vehicle_id: " << device_status.vehicle_id; - return grpc::Status::OK; - } - catch (std::exception &e) - { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message(e.what()); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; - } - } - - // ===================== GetBatteryStatus ===================== - /** - * @brief 查询电池状态。 - * @details 支持 simple 参数,调用设备层 getBatteryStatus。 - * @param context 未使用。 - * @param request 包含设备 ID 和 simple 标志。 - * @param response 返回电池信息。 - */ - grpc::Status gRPCAGVServiceImpl::GetBatteryStatus( - grpc::ServerContext* context, - const api::RobotStatusBatteryCommand_Request* request, - api::RobotStatusBatteryCommand_Feedback* response) - { - try { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (GetBatteryStatus): id=" << dev_id; - const auto agv_device = dmgr_.getDevice(dev_id); - - bool simple = false; - if (request->has_data()) - { - simple = request->data().simple(); - } - - device::AbstractAgv::BatteryStatus bat_info; - agv_device->getBatteryStatus(bat_info, simple); - - auto* proto_bat = response->mutable_status(); - proto_bat->set_battery_level(bat_info.battery_level); - proto_bat->set_battery_temp(bat_info.battery_temp); - proto_bat->set_charging(bat_info.charging); - proto_bat->set_voltage(bat_info.voltage); - proto_bat->set_current(bat_info.current); - proto_bat->set_max_charge_voltage(bat_info.max_charge_voltage); - proto_bat->set_max_charge_current(bat_info.max_charge_current); - proto_bat->set_manual_charge(bat_info.manual_charge); - proto_bat->set_auto_charge(bat_info.auto_charge); - proto_bat->set_battery_cycle(bat_info.battery_cycle); - proto_bat->set_battery_user_data(bat_info.battery_user_data); - proto_bat->set_extra(bat_info.extra); - proto_bat->set_ret_code(bat_info.ret_code); - proto_bat->set_create_on(bat_info.create_on); - proto_bat->set_err_msg(bat_info.err_msg); - - auto* header = response->mutable_header(); - header->set_success(true); - header->set_error_message(""); - setCurrentTimestamp(header->mutable_timestamp()); - - LOG(INFO) << "GetBatteryStatus device_id:" << dev_id << " level: " << bat_info.battery_level; - return grpc::Status::OK; - } - catch (std::exception &e) - { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message(e.what()); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; - } - } - - // ===================== GetRobotLocation ===================== - /** - * @brief 查询机器人当前位置。 - * @param context 未使用。 - * @param request 包含设备 ID。 - * @param response 返回坐标、朝向、置信度等。 - */ - grpc::Status gRPCAGVServiceImpl::GetRobotLocation( - grpc::ServerContext* context, - const api::RobotStatusLocCommand_Request* request, - api::RobotStatusLocCommand_Feedback* response) - { - try { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (GetRobotLocation): id=" << dev_id; - const auto agv_device = dmgr_.getDevice(dev_id); - - device::AbstractAgv::RobotLocation loc_info; - agv_device->getRobotLocation(loc_info); - - auto* proto_loc = response->mutable_status(); - proto_loc->set_x(loc_info.x); - proto_loc->set_y(loc_info.y); - proto_loc->set_angle(loc_info.angle); - proto_loc->set_confidence(loc_info.confidence); - proto_loc->set_current_station(loc_info.current_station); - proto_loc->set_last_station(loc_info.last_station); - proto_loc->set_loc_method(loc_info.loc_method); - proto_loc->set_ret_code(loc_info.ret_code); - proto_loc->set_create_on(loc_info.create_on); - proto_loc->set_err_msg(loc_info.err_msg); - - auto* header = response->mutable_header(); - header->set_success(true); - header->set_error_message(""); - setCurrentTimestamp(header->mutable_timestamp()); - - LOG(INFO) << "GetRobotLocation device_id:" << dev_id << " x: " << loc_info.x << ", y: " << loc_info.y; - return grpc::Status::OK; - } - catch (std::exception &e) - { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message(e.what()); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; - } - } - - // ===================== RobotConfigDownloadMap ===================== - /** - * @brief 下载指定地图。 - * @param context 未使用。 - * @param request 包含设备 ID 和地图名称。 - * @param response 返回地图 JSON 内容或错误码。 - */ - grpc::Status gRPCAGVServiceImpl::RobotConfigDownloadMap( - grpc::ServerContext* context, - const api::RobotConfigDownloadMapCommand_Request* request, - api::RobotConfigDownloadMapCommand_Feedback* response) - { - try { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (RobotConfigDownloadMap): id=" << dev_id; - const auto agv_device = dmgr_.getDevice(dev_id); - - std::string map_name = request->data().map_name(); - device::AbstractAgv::DownloadMapResult down_info; - agv_device->downloadMap(down_info, map_name); - - auto* proto_res = response->mutable_status(); - proto_res->set_ret_code(down_info.ret_code); - proto_res->set_create_on(down_info.create_on); - proto_res->set_err_msg(down_info.err_msg); - proto_res->set_map_content(down_info.map_content); - - auto* header = response->mutable_header(); - header->set_success(true); - header->set_error_message(""); - setCurrentTimestamp(header->mutable_timestamp()); - - LOG(INFO) << "DownloadMap device_id:" << dev_id << " map: " << map_name << ", ret_code: " << down_info.ret_code; - return grpc::Status::OK; - } - catch (std::exception &e) - { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message(e.what()); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; - } - } - - // ===================== GetMapStatus ===================== - /** - * @brief 查询地图状态。 - * @param context 未使用。 - * @param request 包含设备 ID。 - * @param response 返回当前地图、地图列表及文件详情。 - */ - grpc::Status gRPCAGVServiceImpl::GetMapStatus( - grpc::ServerContext* context, - const api::RobotStatusMapCommand_Request* request, - api::RobotStatusMapCommand_Feedback* response) - { - try { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (GetMapStatus): id=" << dev_id; - const auto agv_device = dmgr_.getDevice(dev_id); - - device::AbstractAgv::MapStatus map_info; - agv_device->getMapStatus(map_info); - - auto* proto_map = response->mutable_status(); - proto_map->set_current_map(map_info.current_map); - proto_map->set_current_map_md5(map_info.current_map_md5); - proto_map->set_ret_code(map_info.ret_code); - proto_map->set_create_on(map_info.create_on); - proto_map->set_err_msg(map_info.err_msg); - - for (auto& name : map_info.maps) - { - proto_map->add_maps(name); - } - - for (auto& file : map_info.map_files_info) - { - auto* proto_file = proto_map->add_map_files_info(); - proto_file->set_name(file.name); - proto_file->set_modified(file.modified); - proto_file->set_size(file.size); - } - - auto* header = response->mutable_header(); - header->set_success(true); - header->set_error_message(""); - setCurrentTimestamp(header->mutable_timestamp()); - - LOG(INFO) << "GetMapStatus device_id:" << dev_id << " current map: " << map_info.current_map; - return grpc::Status::OK; - } - catch (std::exception &e) - { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message(e.what()); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; - } - } - - // ===================== RobotConfigUploadMap ===================== - /** - * @brief 上传地图。 - * @param context 未使用。 - * @param request 包含设备 ID 和地图 JSON 字符串。 - * @param response 返回上传结果。 - */ - grpc::Status gRPCAGVServiceImpl::RobotConfigUploadMap( - grpc::ServerContext* context, - const api::RobotConfigUploadMapCommand_Request* request, - api::RobotConfigUploadMapCommand_Feedback* response) - { - try { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (RobotConfigUploadMap): id=" << dev_id; - const auto agv_device = dmgr_.getDevice(dev_id); - - std::string map_json = request->data().map_content(); - device::AbstractAgv::UploadMapResult upload_info; - agv_device->uploadMap(upload_info, map_json); - - auto* proto_res = response->mutable_status(); - proto_res->set_ret_code(upload_info.ret_code); - proto_res->set_create_on(upload_info.create_on); - proto_res->set_err_msg(upload_info.err_msg); - - auto* header = response->mutable_header(); - header->set_success(true); - header->set_error_message(""); - setCurrentTimestamp(header->mutable_timestamp()); - - LOG(INFO) << "UploadMap device_id:" << dev_id << " ret_code: " << upload_info.ret_code; - return grpc::Status::OK; - } - catch (std::exception &e) - { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message(e.what()); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; - } - } - - // ===================== RobotConfigLock ===================== - /** - * @brief 抢占机器人控制权。 - * @param context 未使用。 - * @param request 包含设备 ID 和抢占者昵称。 - * @param response 返回抢占结果。 - */ - grpc::Status gRPCAGVServiceImpl::RobotConfigLock( - grpc::ServerContext* context, - const api::RobotConfigLockCommand_Request* request, - api::RobotConfigLockCommand_Feedback* response) - { - try { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (RobotConfigLock): id=" << dev_id; - - const auto agv_device = dmgr_.getDevice(dev_id); - - std::string nick_name = request->data().nick_name(); - device::AbstractAgv::LockResult lock_res; - agv_device->lockRobotControl(lock_res, nick_name); - - auto* proto_status = response->mutable_status(); - proto_status->set_ret_code(lock_res.ret_code); - proto_status->set_create_on(lock_res.create_on); - proto_status->set_err_msg(lock_res.err_msg); - - auto* header = response->mutable_header(); - header->set_success(true); - header->set_error_message(""); - setCurrentTimestamp(header->mutable_timestamp()); - - LOG(INFO) << "Lock robot control, device_id:" << dev_id << ", nick:" << nick_name << ", ret_code:" << lock_res.ret_code; - return grpc::Status::OK; - } - catch (std::exception &e) - { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message(e.what()); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; - } - } - - // ===================== GetCurrentLockStatus ===================== - /** - * @brief 查询当前控制权持有者。 - * @param context 未使用。 - * @param request 包含设备 ID。 - * @param response 返回锁定状态、持有者信息等。 - */ - grpc::Status gRPCAGVServiceImpl::GetCurrentLockStatus( - grpc::ServerContext* context, - const api::RobotStatusCurrentLockCommand_Request* request, - api::RobotStatusCurrentLockCommand_Feedback* response) - { - try { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (GetCurrentLockStatus): id=" << dev_id; - - const auto agv_dev = dmgr_.getDevice(dev_id); - - device::AbstractAgv::CurrentLockStatus lock_info; - agv_dev->getCurrentLockStatus(lock_info); - - auto* proto_status = response->mutable_status(); - proto_status->set_locked(lock_info.locked); - proto_status->set_ip(lock_info.ip); - proto_status->set_port(lock_info.port); - proto_status->set_type(lock_info.type); - proto_status->set_nick_name(lock_info.nick_name); - proto_status->set_time_t(lock_info.time_t); - proto_status->set_desc(lock_info.desc); - proto_status->set_ret_code(lock_info.ret_code); - proto_status->set_create_on(lock_info.create_on); - proto_status->set_err_msg(lock_info.err_msg); - - response->mutable_header()->set_success(true); - setCurrentTimestamp(response->mutable_header()->mutable_timestamp()); - - LOG(INFO) << "Query current lock status, device_id:" << dev_id - << ", locked:" << lock_info.locked << ", nick:" << lock_info.nick_name; - return grpc::Status::OK; - } - catch (std::exception &e) - { - response->mutable_header()->set_success(false); - response->mutable_header()->set_error_message(e.what()); - setCurrentTimestamp(response->mutable_header()->mutable_timestamp()); - return grpc::Status::OK; - } - } - - // ===================== RobotMotionControl ===================== - /** - * @brief 开环速度运动控制。 - * @param context 未使用。 - * @param request 包含设备 ID 和速度参数。 - * @param response 返回指令下发结果。 - */ - grpc::Status gRPCAGVServiceImpl::RobotMotionControl( - grpc::ServerContext* context, - const api::RobotMotionControlCommand_Request* request, - api::RobotMotionControlCommand_Feedback* response) - { - try { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (RobotMotionControl): target device_id=" << dev_id; - const auto agv_dev = dmgr_.getDevice(dev_id); - - device::AbstractAgv::MotionCtrlReq req; - auto& input_data = request->data(); - req.vx = input_data.vx(); - req.vy = input_data.vy(); - req.w = input_data.w(); - req.steer = input_data.steer(); - req.real_steer = input_data.real_steer(); - req.duration = input_data.duration(); - - device::AbstractAgv::MotionCtrlRes res; - agv_dev->robotMotionControl(res, req); - - auto* resp_status = response->mutable_status(); - resp_status->set_ret_code(res.ret_code); - resp_status->set_create_on(res.create_on); - resp_status->set_err_msg(res.err_msg); - - auto* header = response->mutable_header(); - header->set_success(true); - header->set_error_message(""); - setCurrentTimestamp(header->mutable_timestamp()); - - LOG(INFO) << "RobotMotionControl finish, dev_id:" << dev_id - << " vx=" << req.vx << " vy=" << req.vy << " w=" << req.w - << " ret_code=" << res.ret_code; - return grpc::Status::OK; - } - catch (std::exception& e) - { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message(e.what()); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; - } - } - - // ===================== RobotLoadMap ===================== - /** - * @brief 切换载入地图。 - * @param context 未使用。 - * @param request 包含设备 ID 和目标地图名称。 - * @param response 返回切换结果。 - */ - grpc::Status gRPCAGVServiceImpl::RobotLoadMap( - grpc::ServerContext* context, - const api::RobotLoadMapCommand_Request* request, - api::RobotLoadMapCommand_Feedback* response) - { - try { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (RobotLoadMap): target device_id=" << dev_id; - const auto agv_dev = dmgr_.getDevice(dev_id); - - device::AbstractAgv::LoadMapReq req; - auto& input_data = request->data(); - req.map_name = input_data.map_name(); - - device::AbstractAgv::LoadMapRes res; - agv_dev->robotLoadMap(res, req); - - auto* resp_status = response->mutable_status(); - resp_status->set_ret_code(res.ret_code); - resp_status->set_create_on(res.create_on); - resp_status->set_err_msg(res.err_msg); - - auto* header = response->mutable_header(); - header->set_success(true); - header->set_error_message(""); - setCurrentTimestamp(header->mutable_timestamp()); - - LOG(INFO) << "RobotLoadMap finish, dev_id:" << dev_id - << " map_name=" << req.map_name - << " ret_code=" << res.ret_code; - return grpc::Status::OK; - } - catch (std::exception& e) - { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message(e.what()); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; - } - } - - // ===================== QueryLoadMapStatus ===================== - /** - * @brief 查询地图载入状态。 - * @param context 未使用。 - * @param request 包含设备 ID。 - * @param response 返回 loadmap_status。 - */ - grpc::Status gRPCAGVServiceImpl::QueryLoadMapStatus( - grpc::ServerContext* context, - const api::RobotQueryLoadMapStatusCommand_Request* request, - api::RobotQueryLoadMapStatusCommand_Feedback* response) - { - try { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (QueryLoadMapStatus): device_id=" << dev_id; - const auto agv_dev = dmgr_.getDevice(dev_id); - - device::AbstractAgv::QueryLoadMapStatusRes res; - agv_dev->queryLoadMapStatus(res); - - auto* resp_status = response->mutable_status(); - resp_status->set_loadmap_status(res.loadmap_status); - resp_status->set_ret_code(res.ret_code); - resp_status->set_create_on(res.create_on); - resp_status->set_err_msg(res.err_msg); - - auto* header = response->mutable_header(); - header->set_success(true); - header->set_error_message(""); - setCurrentTimestamp(header->mutable_timestamp()); - - LOG(INFO) << "QueryLoadMapStatus finish dev_id:" << dev_id - << " loadmap_status=" << res.loadmap_status - << " ret_code=" << res.ret_code; - return grpc::Status::OK; - } - catch (std::exception& e) - { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message(e.what()); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; - } - } - - // ===================== QueryStationList ===================== - /** - * @brief 查询当前地图所有站点信息。 - * @param context 未使用。 - * @param request 包含设备 ID。 - * @param response 返回站点列表。 - */ - grpc::Status gRPCAGVServiceImpl::QueryStationList( - grpc::ServerContext* context, - const api::QueryStationListCommand_Request* request, - api::QueryStationListCommand_Feedback* response) - { - try { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (QueryStationList): device_id=" << dev_id; - const auto agv_dev = dmgr_.getDevice(dev_id); - - device::AbstractAgv::QueryStationRes res; - agv_dev->queryStationList(res); - - auto* resp_status = response->mutable_status(); - resp_status->set_ret_code(res.ret_code); - resp_status->set_create_on(res.create_on); - resp_status->set_err_msg(res.err_msg); - - for (auto& st : res.stations) - { - auto* pb_st = resp_status->add_stations(); - pb_st->set_id(st.id); - pb_st->set_type(st.type); - pb_st->set_x(st.x); - pb_st->set_y(st.y); - pb_st->set_r(st.r); - pb_st->set_desc(st.desc); - pb_st->set_executor(st.executor); - pb_st->set_prepoint(st.prepoint); - pb_st->set_recfile(st.recfile); - pb_st->set_spin(st.spin); - pb_st->set_use_down_pgv(st.use_down_pgv); - } - - auto* header = response->mutable_header(); - header->set_success(true); - header->set_error_message(""); - setCurrentTimestamp(header->mutable_timestamp()); - - LOG(INFO) << "QueryStationList finish dev_id:" << dev_id - << " station_count=" << res.stations.size() - << " ret_code=" << res.ret_code; - return grpc::Status::OK; - } - catch (std::exception& e) - { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message(e.what()); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; - } - } - - // ===================== RobotGoTargetList ===================== - /** - * @brief 指定路径导航(多点)。 - * @param context 未使用。 - * @param request 包含设备 ID 和 move_task_list。 - * @param response 返回下发结果(仅表示指令被接收)。 - */ - grpc::Status gRPCAGVServiceImpl::RobotGoTargetList( - grpc::ServerContext* context, - const api::RobotGoTargetListCommand_Request* request, - api::RobotGoTargetListCommand_Feedback* response) - { - try { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (RobotGoTargetList): target device_id=" << dev_id; - const auto agv_dev = dmgr_.getDevice(dev_id); - - device::AbstractAgv::GoTargetListReq req; - const auto& pb_list = request->data().move_task_list(); - for (const auto& pb_item : pb_list) - { - device::AbstractAgv::MoveTaskItem task; - task.task_id = pb_item.task_id(); - task.source_id = pb_item.source_id(); - task.id = pb_item.id(); - task.operation = pb_item.operation(); - task.jack_height = pb_item.jack_height(); - req.move_task_list.push_back(task); - } - - device::AbstractAgv::GoTargetListRes res; - agv_dev->robotGoTargetList(res, req); - - auto* resp_status = response->mutable_status(); - resp_status->set_ret_code(res.ret_code); - resp_status->set_create_on(res.create_on); - resp_status->set_err_msg(res.err_msg); - - auto* header = response->mutable_header(); - header->set_success(true); - header->set_error_message(""); - setCurrentTimestamp(header->mutable_timestamp()); - - LOG(INFO) << "RobotGoTargetList finish dev_id:" << dev_id - << " task_count=" << req.move_task_list.size() - << " ret_code=" << res.ret_code; - return grpc::Status::OK; - } - catch (std::exception& e) - { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message(e.what()); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; - } - } - - // ===================== RobotStatusTaskCurrent ===================== - /** - * @brief 查询当前实时导航状态。 - * @param context 未使用。 - * @param request 包含设备 ID 和 simple 标志。 - * @param response 返回任务状态、类型、目标、路径等。 - */ - grpc::Status gRPCAGVServiceImpl::RobotStatusTaskCurrent( - grpc::ServerContext* context, - const api::RobotStatusTaskCurrentCommand_Request* request, - api::RobotStatusTaskCurrentCommand_Feedback* response) - { - try { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (RobotStatusTaskCurrent): id=" << dev_id; - const auto agv_dev = dmgr_.getDevice(dev_id); - - device::AbstractAgv::RobotStatusTaskCurrentReq req; - if (request->has_data()) - { - req.simple = request->data().simple(); - } - - device::AbstractAgv::RobotStatusTaskCurrentRes res; - agv_dev->robotStatusTaskCurrent(res, req); - - auto* out_data = response->mutable_data(); - out_data->set_ret_code(res.ret_code); - out_data->set_create_on(res.create_on); - out_data->set_err_msg(res.err_msg); - - out_data->set_task_status(res.task_status); - out_data->set_task_type(res.task_type); - out_data->set_target_id(res.target_id); - out_data->set_move_status_info(res.move_status_info); - - for (double p : res.target_point) - out_data->add_target_point(p); - for (auto& s : res.finished_path) - out_data->add_finished_path(s); - for (auto& s : res.unfinished_path) - out_data->add_unfinished_path(s); - - for (auto& c : res.containers) - { - auto* pc = out_data->add_containers(); - pc->set_container_name(c.container_name); - pc->set_desc(c.desc); - pc->set_goods_id(c.goods_id); - pc->set_has_goods(c.has_goods); - } - - auto* header = response->mutable_header(); - header->set_success(true); - header->set_error_message(""); - setCurrentTimestamp(header->mutable_timestamp()); - - LOG(INFO) << "RobotStatusTaskCurrent dev_id:" << dev_id << " task_status:" << res.task_status; - return grpc::Status::OK; - } - catch (std::exception &e) - { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message(e.what()); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; - } - } - - // ===================== RobotStatusTaskPackage ===================== - /** - * @brief 批量查询任务状态。 - * @param context 未使用。 - * @param request 包含设备 ID 和 task_ids 列表。 - * @param response 返回任务状态包(进度、距离、每个任务状态等)。 - */ - grpc::Status gRPCAGVServiceImpl::RobotStatusTaskPackage( - grpc::ServerContext* context, - const api::RobotStatusTaskPackageCommand_Request* request, - api::RobotStatusTaskPackageCommand_Feedback* response) - { - try { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[gRPCAGVServiceImpl] (RobotStatusTaskPackage): id=" << dev_id; - const auto agv_dev = dmgr_.getDevice(dev_id); - - device::AbstractAgv::QueryTaskStatusPackageReq req; - const auto& pb_ids = request->data().task_ids(); - for (auto& tid : pb_ids) - { - req.task_ids.push_back(tid); - } - - device::AbstractAgv::QueryTaskStatusPackageRes res; - agv_dev->robotStatusTaskPackage(res, req); - - auto* out_data = response->mutable_data(); - out_data->set_ret_code(res.ret_code); - out_data->set_create_on(res.create_on); - out_data->set_err_msg(res.err_msg); - - auto* pkg_pb = out_data->mutable_task_status_package(); - auto& pkg_data = res.task_status_package; - pkg_pb->set_closest_target(pkg_data.closest_target); - pkg_pb->set_source_name(pkg_data.source_name); - pkg_pb->set_target_name(pkg_data.target_name); - pkg_pb->set_percentage(pkg_data.percentage); - pkg_pb->set_distance(pkg_data.distance); - pkg_pb->set_info(pkg_data.info); - - for (auto& st_item : pkg_data.task_status_list) - { - auto* st_pb = pkg_pb->add_task_status_list(); - st_pb->set_task_id(st_item.task_id); - st_pb->set_status(st_item.status); - st_pb->set_type(st_item.type); - } - - auto* header = response->mutable_header(); - header->set_success(true); - header->set_error_message(""); - setCurrentTimestamp(header->mutable_timestamp()); - - LOG(INFO) << "RobotStatusTaskPackage dev_id:" << dev_id << " query task count:" << req.task_ids.size() << " ret_code:" << res.ret_code; - return grpc::Status::OK; - } - catch (std::exception &e) - { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message(e.what()); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; - } - } - - // ===================== RobotGoTarget ===================== - /** - * @brief 单点站点自动规划导航。 - * @param context 未使用。 - * @param request 包含设备 ID 和完整的导航参数(支持自由导航、动作、PGV、脚本等)。 - * @param response 返回下发结果。 - * @warning 严禁用于多车调度场景,仅限单车测试。 - */ - grpc::Status gRPCAGVServiceImpl::RobotGoTarget( - grpc::ServerContext* context, - const api::RobotGoTargetCommand_Request* request, - api::RobotGoTargetCommand_Feedback* response) - { - try - { - std::string dev_id = request->header().device_id(); - LOG(INFO) << "[RobotGoTarget] device_id:" << dev_id; - auto agv_dev = dmgr_.getDevice(dev_id); - if (!agv_dev) - { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message("device not found"); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; - } - - device::AbstractAgv::RobotGoTargetReq req; - auto& pb_data = request->data(); - - // -------- 必填字段 -------- - req.source_id = pb_data.source_id(); - req.id = pb_data.id(); - if (pb_data.has_task_id()) req.task_id = pb_data.task_id(); - - // -------- 运动参数 -------- - if (pb_data.has_angle()) req.angle = pb_data.angle(); - if (pb_data.has_method()) req.method = pb_data.method(); - if (pb_data.has_max_speed()) req.max_speed = pb_data.max_speed(); - if (pb_data.has_max_wspeed()) req.max_wspeed = pb_data.max_wspeed(); - if (pb_data.has_max_acc()) req.max_acc = pb_data.max_acc(); - if (pb_data.has_max_wacc()) req.max_wacc = pb_data.max_wacc(); - if (pb_data.has_duration()) req.duration = pb_data.duration(); - if (pb_data.has_orientation()) req.orientation = pb_data.orientation(); - if (pb_data.has_spin()) req.spin = pb_data.spin(); - if (pb_data.has_delay()) req.delay = pb_data.delay(); - if (pb_data.has_start_rot_dir()) req.start_rot_dir = pb_data.start_rot_dir(); - if (pb_data.has_end_rot_dir()) req.end_rot_dir = pb_data.end_rot_dir(); - if (pb_data.has_reach_dist()) req.reach_dist = pb_data.reach_dist(); - if (pb_data.has_reach_angle()) req.reach_angle = pb_data.reach_angle(); - if (pb_data.has_skill_name()) req.skill_name = pb_data.skill_name(); - - // -------- PGV 二次定位 -------- - if (pb_data.has_pgv()) - { - auto& pgv_pb = pb_data.pgv(); - req.pgv.use_pgv = pgv_pb.use_pgv(); - req.pgv.use_down_pgv = pgv_pb.use_down_pgv(); - if (pgv_pb.has_pgv_adjust_dist()) req.pgv.pgv_adjust_dist = pgv_pb.pgv_adjust_dist(); - if (pgv_pb.has_pgv_adjust_cx()) req.pgv.pgv_adjust_cx = pgv_pb.pgv_adjust_cx(); - if (pgv_pb.has_pgv_adjust_cy()) req.pgv.pgv_adjust_cy = pgv_pb.pgv_adjust_cy(); - if (pgv_pb.has_pgv_x_adjust()) req.pgv.pgv_x_adjust = pgv_pb.pgv_x_adjust(); - } - - // -------- 设备动作 -------- - if (pb_data.has_operation()) req.operation = pb_data.operation(); - if (pb_data.has_jack_height()) req.jack_height = pb_data.jack_height(); - if (pb_data.has_start_height()) req.start_height = pb_data.start_height(); - if (pb_data.has_end_height()) req.end_height = pb_data.end_height(); - if (pb_data.has_fork_mid_height()) req.fork_mid_height = pb_data.fork_mid_height(); - if (pb_data.has_fork_dist()) req.fork_dist = pb_data.fork_dist(); - if (pb_data.has_direction()) req.direction = pb_data.direction(); - if (pb_data.has_recognize()) req.recognize = pb_data.recognize(); - if (pb_data.has_recfile()) req.recfile = pb_data.recfile(); - - // -------- 音频 -------- - if (pb_data.has_sounds_args()) - { - auto& s_pb = pb_data.sounds_args(); - if (s_pb.has_name()) req.sounds_args.name = s_pb.name(); - if (s_pb.has_loop()) req.sounds_args.loop = s_pb.loop(); - if (s_pb.has_stop()) req.sounds_args.stop = s_pb.stop(); - } - - // -------- WaitDI -------- - if (pb_data.has_wait_di_args()) - { - auto& di_pb = pb_data.wait_di_args(); - req.wait_di_args.timeout = di_pb.timeout(); - for (auto& item : di_pb.di()) - { - device::AbstractAgv::DIItem di; - di.id = item.id(); - di.status = item.status(); - req.wait_di_args.DI.push_back(di); - } - } - - // -------- SetDO -------- - if (pb_data.has_set_do_args()) - { - auto& do_pb = pb_data.set_do_args(); - for (auto& item : do_pb.do_list()) - { - device::AbstractAgv::DOItem d; - d.id = item.id(); - d.status = item.status(); - req.set_do_args.DO.push_back(d); - } - } - - // -------- 脚本 -------- - if (pb_data.has_script_name()) req.script_name = pb_data.script_name(); - if (pb_data.has_script_stage()) req.script_stage = pb_data.script_stage(); - if (pb_data.has_script_args()) - { - auto& s_arg_pb = pb_data.script_args(); - for (auto& kv : s_arg_pb.str_kv()) req.script_args.str_kv[kv.first] = kv.second; - for (auto& kv : s_arg_pb.num_kv()) req.script_args.num_kv[kv.first] = kv.second; - for (auto& d : s_arg_pb.do_list()) - { - device::AbstractAgv::DOItem di; - di.id = d.id(); - di.status = d.status(); - req.script_args.do_list.push_back(di); - } - for (auto& di : s_arg_pb.di_list()) - { - device::AbstractAgv::DIItem d; - d.id = di.id(); - d.status = di.status(); - req.script_args.di_list.push_back(d); - } - } - - // -------- 原地旋转 -------- - if (pb_data.has_move_angle()) req.move_angle = pb_data.move_angle(); - if (pb_data.has_speed_w()) req.speed_w = pb_data.speed_w(); - if (pb_data.has_loc_mode()) req.loc_mode = pb_data.loc_mode(); - - // -------- 自由导航 -------- - if (pb_data.has_freego()) - { - auto& fg_pb = pb_data.freego(); - req.freeGo.x = fg_pb.x(); - req.freeGo.y = fg_pb.y(); - req.freeGo.theta = fg_pb.theta(); - } - - // 调用设备层 - device::AbstractAgv::RobotGoTargetRes res; - agv_dev->robotGoTarget(res, req); - - auto* out_data = response->mutable_data(); - out_data->set_ret_code(res.ret_code); - out_data->set_create_on(res.create_on); - out_data->set_err_msg(res.err_msg); - - auto* header = response->mutable_header(); - header->set_success(true); - header->set_error_message(""); - setCurrentTimestamp(header->mutable_timestamp()); - - LOG(INFO) << "[RobotGoTarget] finish ret_code=" << res.ret_code; - return grpc::Status::OK; - } - catch (std::exception& e) - { - auto* header = response->mutable_header(); - header->set_success(false); - header->set_error_message(std::string("exception:") + e.what()); - setCurrentTimestamp(header->mutable_timestamp()); - return grpc::Status::OK; - } - } - - // ===================== RobotControlStop ===================== - /** - * @brief 停止开环运动。 - * @param context 未使用。 - * @param request 包含设备 ID(无业务数据)。 - * @param response 返回停止结果。 - */ - grpc::Status gRPCAGVServiceImpl::RobotControlStop( - grpc::ServerContext* context, - const api::RobotControlStopCommand::Request* request, - api::RobotControlStopCommand::Feedback* response) { - - const auto& header = request->header(); - std::string device_id = header.device_id(); - auto* fb_header = response->mutable_header(); - - if (device_id.empty()) { - fb_header->set_error_message("Missing device_id"); - return grpc::Status::OK; - } - - auto agv = dmgr_.getDevice(device_id); - if (!agv) { - fb_header->set_error_message("Device not found or not an AGV"); - return grpc::Status::OK; - } - - cmvr::device::AbstractAgv::RobotControlStopRes res; - agv->robotControlStop(res); - - auto* status = response->mutable_status(); - status->set_ret_code(res.ret_code); - status->set_create_on(res.create_on); - status->set_err_msg(res.err_msg); - - if (res.ret_code == 0) { - fb_header->set_error_message(""); - } else { - fb_header->set_error_message(res.err_msg.empty() ? "Stop motion failed" : res.err_msg); - } - - return grpc::Status::OK; - } - - // ===================== RobotTaskPause ===================== - /** - * @brief 暂停当前导航任务。 - * @param context 未使用。 - * @param request 包含设备 ID。 - * @param response 返回暂停结果。 - */ - grpc::Status gRPCAGVServiceImpl::RobotTaskPause( - grpc::ServerContext* context, - const api::RobotTaskPauseCommand::Request* request, - api::RobotTaskPauseCommand::Feedback* response) { - - const auto& header = request->header(); - std::string device_id = header.device_id(); - auto* fb_header = response->mutable_header(); - - if (device_id.empty()) { - fb_header->set_error_message("Missing device_id"); - return grpc::Status::OK; - } - - auto agv = dmgr_.getDevice(device_id); - if (!agv) { - fb_header->set_error_message("Device not found or not an AGV"); - return grpc::Status::OK; - } - - cmvr::device::AbstractAgv::RobotTaskPauseRes res; - try { - agv->robotTaskPause(res); - } catch (const std::exception& e) { - fb_header->set_error_message(std::string("Exception: ") + e.what()); - return grpc::Status::OK; - } - - auto* status = response->mutable_status(); - status->set_ret_code(res.ret_code); - status->set_create_on(res.create_on); - status->set_err_msg(res.err_msg); - - if (res.ret_code != 0) { - fb_header->set_error_message(res.err_msg.empty() ? "Pause task failed" : res.err_msg); - } else { - fb_header->set_error_message(""); - } - - return grpc::Status::OK; - } - - // ===================== RobotTaskResume ===================== - /** - * @brief 继续当前导航任务。 - * @param context 未使用。 - * @param request 包含设备 ID。 - * @param response 返回继续结果。 - */ - grpc::Status gRPCAGVServiceImpl::RobotTaskResume( - grpc::ServerContext* context, - const api::RobotTaskResumeCommand::Request* request, - api::RobotTaskResumeCommand::Feedback* response) { - - const auto& header = request->header(); - std::string device_id = header.device_id(); - auto* fb_header = response->mutable_header(); - - if (device_id.empty()) { - fb_header->set_error_message("Missing device_id"); - return grpc::Status::OK; - } - - auto agv = dmgr_.getDevice(device_id); - if (!agv) { - fb_header->set_error_message("Device not found or not an AGV"); - return grpc::Status::OK; - } - - cmvr::device::AbstractAgv::RobotTaskResumeRes res; - try { - agv->robotTaskResume(res); - } catch (const std::exception& e) { - fb_header->set_error_message(std::string("Exception: ") + e.what()); - return grpc::Status::OK; - } - - auto* status = response->mutable_status(); - status->set_ret_code(res.ret_code); - status->set_create_on(res.create_on); - status->set_err_msg(res.err_msg); - - if (res.ret_code != 0) { - fb_header->set_error_message(res.err_msg.empty() ? "Resume task failed" : res.err_msg); - } else { - fb_header->set_error_message(""); - } - - return grpc::Status::OK; - } - - // ===================== RobotTaskCancel ===================== - /** - * @brief 取消当前导航任务。 - * @param context 未使用。 - * @param request 包含设备 ID。 - * @param response 返回取消结果。 - */ - grpc::Status gRPCAGVServiceImpl::RobotTaskCancel( - grpc::ServerContext* context, - const api::RobotTaskCancelCommand::Request* request, - api::RobotTaskCancelCommand::Feedback* response) { - - const auto& header = request->header(); - std::string device_id = header.device_id(); - auto* fb_header = response->mutable_header(); - - if (device_id.empty()) { - fb_header->set_error_message("Missing device_id"); - return grpc::Status::OK; - } - - auto agv = dmgr_.getDevice(device_id); - if (!agv) { - fb_header->set_error_message("Device not found or not an AGV"); - return grpc::Status::OK; - } - - cmvr::device::AbstractAgv::RobotTaskCancelRes res; - try { - agv->robotTaskCancel(res); - } catch (const std::exception& e) { - fb_header->set_error_message(std::string("Exception: ") + e.what()); - return grpc::Status::OK; - } - - auto* status = response->mutable_status(); - status->set_ret_code(res.ret_code); - status->set_create_on(res.create_on); - status->set_err_msg(res.err_msg); - - if (res.ret_code != 0) { - fb_header->set_error_message(res.err_msg.empty() ? "Cancel task failed" : res.err_msg); - } else { - fb_header->set_error_message(""); - } - - return grpc::Status::OK; - } - -} // namespace cmvr::service \ No newline at end of file diff --git a/cmvr-es/service/grpc/src/grpc_humanoid_robot_service.cpp b/cmvr-es/service/grpc/src/grpc_humanoid_robot_service.cpp index 0c4e8469..487fe705 100644 --- a/cmvr-es/service/grpc/src/grpc_humanoid_robot_service.cpp +++ b/cmvr-es/service/grpc/src/grpc_humanoid_robot_service.cpp @@ -17,7 +17,7 @@ using google::protobuf::util::TimeUtil; gRPCHumanoidRobotServiceImpl::gRPCHumanoidRobotServiceImpl():dmgr_(DeviceManager::getInstance()) { - pinocchio_ik_ = std::make_shared("/home/cmvr/Projects/cmvr-es/model/xiaoyan_description/dual_arm.urdf" + pinocchio_ik_ = std::make_shared("/home/linbo/cmvr/cmvr-es/model/xiaoyan_description/dual_arm.urdf" ,"PELVIS_S" ,"R_WRIST_R_S"); pinocchio_ik_->init(); diff --git a/cmvr-es/service/grpc/src/server_runner.cpp b/cmvr-es/service/grpc/src/server_runner.cpp index c2817179..dc689aff 100644 --- a/cmvr-es/service/grpc/src/server_runner.cpp +++ b/cmvr-es/service/grpc/src/server_runner.cpp @@ -22,8 +22,6 @@ #include "service/grpc/include/grpc_hlc_service.h" #include "json/json.h" #include -#include "service/grpc/include/grpc_agv_service.h" -#include "common/utils/config_helper/include/config_helper.h" using namespace cmvr::service; ServerRunner::ServerRunner() = default; @@ -99,7 +97,9 @@ void ServerRunner::threadMain() { using namespace cmvr::monitor; try { - + if (!cfg_.hasChild("DeviceManager")) { + throw std::runtime_error("Device Manager node not found"); + } static std::once_flag reflection_once; std::call_once(reflection_once, []() { @@ -109,8 +109,13 @@ void ServerRunner::threadMain() { auto dmgr_cfg = cfg_.getChild("DeviceManager"); DeviceManager::getInstance(dmgr_cfg); - cmvr::ConfigHelper::getServerConfig(servercfg_); - std::string port = servercfg_.grpc_server().port(); + if (cfg_.hasChild("MonitorManager")) { + auto mmgr_cfg = cfg_.getChild("MonitorManager"); + MonitorManager::getInstance(mmgr_cfg); + } + + auto grpc_cfg = cfg_.getChild("gRPCServer"); + std::string port = grpc_cfg.getAttrDefault("port", "50051"); std::string local_address = "0.0.0.0:" + port; auto camera_service = std::make_unique(); @@ -121,7 +126,6 @@ void ServerRunner::threadMain() { auto biohand_service = std::make_unique(); auto humanoid_robot_service = std::make_unique(); auto hlc_service = std::make_unique(); - auto agv_service = std::make_unique(); grpc::ServerBuilder builder; builder.AddListeningPort(local_address, grpc::InsecureServerCredentials()); @@ -133,7 +137,6 @@ void ServerRunner::threadMain() { builder.RegisterService(biohand_service.get()); builder.RegisterService(humanoid_robot_service.get()); builder.RegisterService(hlc_service.get()); - builder.RegisterService(agv_service.get()); auto local_server = builder.BuildAndStart(); if (!local_server) { diff --git a/dependency/x86/third_party/soem/include/soem/ec_base.h b/dependency/x86/third_party/soem/include/soem/ec_base.h deleted file mode 100644 index 3eafa121..00000000 --- a/dependency/x86/third_party/soem/include/soem/ec_base.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * This software is dual-licensed under GPLv3 and a commercial - * license. See the file LICENSE.md distributed with this software for - * full license information. - */ - -/** \file - * \brief - * Headerfile for ec_base.c - */ - -#ifndef _ec_base_ -#define _ec_base_ - -#ifdef __cplusplus -extern "C" { -#endif - -int ecx_setupdatagram(ecx_portt *port, void *frame, uint8 com, uint8 idx, uint16 ADP, uint16 ADO, uint16 length, void *data); -uint16 ecx_adddatagram(ecx_portt *port, void *frame, uint8 com, uint8 idx, boolean more, uint16 ADP, uint16 ADO, uint16 length, void *data); -int ecx_BWR(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 length, void *data, int timeout); -int ecx_BRD(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 length, void *data, int timeout); -int ecx_APRD(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 length, void *data, int timeout); -int ecx_ARMW(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 length, void *data, int timeout); -int ecx_FRMW(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 length, void *data, int timeout); -uint16 ecx_APRDw(ecx_portt *port, uint16 ADP, uint16 ADO, int timeout); -int ecx_FPRD(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 length, void *data, int timeout); -uint16 ecx_FPRDw(ecx_portt *port, uint16 ADP, uint16 ADO, int timeout); -int ecx_APWRw(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 data, int timeout); -int ecx_APWR(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 length, void *data, int timeout); -int ecx_FPWRw(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 data, int timeout); -int ecx_FPWR(ecx_portt *port, uint16 ADP, uint16 ADO, uint16 length, void *data, int timeout); -int ecx_LRW(ecx_portt *port, uint32 LogAdr, uint16 length, void *data, int timeout); -int ecx_LRD(ecx_portt *port, uint32 LogAdr, uint16 length, void *data, int timeout); -int ecx_LWR(ecx_portt *port, uint32 LogAdr, uint16 length, void *data, int timeout); -int ecx_LRWDC(ecx_portt *port, uint32 LogAdr, uint16 length, void *data, uint16 DCrs, int64 *DCtime, int timeout); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_coe.h b/dependency/x86/third_party/soem/include/soem/ec_coe.h deleted file mode 100644 index 45c889e5..00000000 --- a/dependency/x86/third_party/soem/include/soem/ec_coe.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * This software is dual-licensed under GPLv3 and a commercial - * license. See the file LICENSE.md distributed with this software for - * full license information. - */ - -/** \file - * \brief - * Headerfile for ec_coe.c - */ - -#ifndef _ec_coe_ -#define _ec_coe_ - -#ifdef __cplusplus -extern "C" { -#endif - -/* Storage for object description list */ -typedef struct -{ - /** slave number */ - uint16 Slave; - /** number of entries in list */ - uint16 Entries; - /** array of indexes */ - uint16 Index[EC_MAXODLIST]; - /** array of datatypes, see EtherCAT specification */ - uint16 DataType[EC_MAXODLIST]; - /** array of object codes, see EtherCAT specification */ - uint8 ObjectCode[EC_MAXODLIST]; - /** number of subindexes for each index */ - uint8 MaxSub[EC_MAXODLIST]; - /** textual description of each index */ - char Name[EC_MAXODLIST][EC_MAXNAME + 1]; -} ec_ODlistt; - -/* storage for object list entry information */ -typedef struct -{ - /** number of entries in list */ - uint16 Entries; - /** array of value infos, see EtherCAT specification */ - uint8 ValueInfo[EC_MAXOELIST]; - /** array of value infos, see EtherCAT specification */ - uint16 DataType[EC_MAXOELIST]; - /** array of bit lengths, see EtherCAT specification */ - uint16 BitLength[EC_MAXOELIST]; - /** array of object access bits, see EtherCAT specification */ - uint16 ObjAccess[EC_MAXOELIST]; - /** textual description of each index */ - char Name[EC_MAXOELIST][EC_MAXNAME + 1]; -} ec_OElistt; - -void ecx_SDOerror(ecx_contextt *context, uint16 Slave, uint16 Index, uint8 SubIdx, int32 AbortCode); -int ecx_SDOread(ecx_contextt *context, uint16 slave, uint16 index, uint8 subindex, - boolean CA, int *psize, void *p, int timeout); -int ecx_SDOwrite(ecx_contextt *context, uint16 Slave, uint16 Index, uint8 SubIndex, - boolean CA, int psize, const void *p, int Timeout); -int ecx_RxPDO(ecx_contextt *context, uint16 Slave, uint16 RxPDOnumber, int psize, const void *p); -int ecx_TxPDO(ecx_contextt *context, uint16 slave, uint16 TxPDOnumber, int *psize, void *p, int timeout); -int ecx_readPDOmap(ecx_contextt *context, uint16 Slave, uint32 *Osize, uint32 *Isize); -int ecx_readPDOmapCA(ecx_contextt *context, uint16 Slave, int Thread_n, uint32 *Osize, uint32 *Isize); -int ecx_readODlist(ecx_contextt *context, uint16 Slave, ec_ODlistt *pODlist); -int ecx_readODdescription(ecx_contextt *context, uint16 Item, ec_ODlistt *pODlist); -int ecx_readOEsingle(ecx_contextt *context, uint16 Item, uint8 SubI, ec_ODlistt *pODlist, ec_OElistt *pOElist); -int ecx_readOE(ecx_contextt *context, uint16 Item, ec_ODlistt *pODlist, ec_OElistt *pOElist); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_config.h b/dependency/x86/third_party/soem/include/soem/ec_config.h deleted file mode 100644 index c0291177..00000000 --- a/dependency/x86/third_party/soem/include/soem/ec_config.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * This software is dual-licensed under GPLv3 and a commercial - * license. See the file LICENSE.md distributed with this software for - * full license information. - */ - -/** \file - * \brief - * Headerfile for ec_config.c - */ - -#ifndef _ec_config_ -#define _ec_config_ - -#ifdef __cplusplus -extern "C" { -#endif - -#define EC_NODEOFFSET 0x1000 -#define EC_TEMPNODE 0xffff - -int ecx_config_init(ecx_contextt *context); -int ecx_config_map_group(ecx_contextt *context, void *pIOmap, uint8 group); -int ecx_recover_slave(ecx_contextt *context, uint16 slave, int timeout); -int ecx_reconfig_slave(ecx_contextt *context, uint16 slave, int timeout); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_dc.h b/dependency/x86/third_party/soem/include/soem/ec_dc.h deleted file mode 100644 index d0472dd3..00000000 --- a/dependency/x86/third_party/soem/include/soem/ec_dc.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * This software is dual-licensed under GPLv3 and a commercial - * license. See the file LICENSE.md distributed with this software for - * full license information. - */ - -/** \file - * \brief - * Headerfile for ec_dc.c - */ - -#ifndef _EC_ECATDC_H -#define _EC_ECATDC_H - -#ifdef __cplusplus -extern "C" { -#endif - -boolean ecx_configdc(ecx_contextt *context); -void ecx_dcsync0(ecx_contextt *context, uint16 slave, boolean act, uint32 CyclTime, int32 CyclShift); -void ecx_dcsync01(ecx_contextt *context, uint16 slave, boolean act, uint32 CyclTime0, uint32 CyclTime1, int32 CyclShift); - -#ifdef __cplusplus -} -#endif - -#endif /* _EC_ECATDC_H */ diff --git a/dependency/x86/third_party/soem/include/soem/ec_eoe.h b/dependency/x86/third_party/soem/include/soem/ec_eoe.h deleted file mode 100644 index 27ca216e..00000000 --- a/dependency/x86/third_party/soem/include/soem/ec_eoe.h +++ /dev/null @@ -1,213 +0,0 @@ -/* - * This software is dual-licensed under GPLv3 and a commercial - * license. See the file LICENSE.md distributed with this software for - * full license information. - */ - -/** \file - * \brief - * Headerfile for ec_eoe.c - */ - -#ifndef _ec_eoe_ -#define _ec_eoe_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "soem/soem.h" - -/* use maximum size for EOE mailbox data - mbxheader and 2x frameinfo */ -#define EC_MAXEOEDATA (EC_MAXMBX - (sizeof(ec_mbxheadert) + \ - sizeof(uint16_t) + \ - sizeof(uint16_t))) - -/** DNS length according to ETG 1000.6 */ -#define EOE_DNS_NAME_LENGTH 32 -/** Ethernet address length not including VLAN */ -#define EOE_ETHADDR_LENGTH 6 -/** IPv4 address length */ -#define EOE_IP4_LENGTH sizeof(uint32_t) - -#define EOE_MAKEU32(a, b, c, d) (((uint32_t)((a) & 0xff) << 24) | \ - ((uint32_t)((b) & 0xff) << 16) | \ - ((uint32_t)((c) & 0xff) << 8) | \ - (uint32_t)((d) & 0xff)) - -#if !defined(EC_BIG_ENDIAN) && defined(EC_LITTLE_ENDIAN) - -#define EOE_HTONS(x) ((((x) & 0x00ffUL) << 8) | (((x) & 0xff00UL) >> 8)) -#define EOE_NTOHS(x) EOE_HTONS(x) -#define EOE_HTONL(x) ((((x) & 0x000000ffUL) << 24) | \ - (((x) & 0x0000ff00UL) << 8) | \ - (((x) & 0x00ff0000UL) >> 8) | \ - (((x) & 0xff000000UL) >> 24)) -#define EOE_NTOHL(x) EOE_HTONL(x) -#else -#define EOE_HTONS(x) (x) -#define EOE_NTOHS(x) (x) -#define EOE_HTONL(x) (x) -#define EOE_NTOHL(x) (x) -#endif /* !defined(EC_BIG_ENDIAN) && defined(EC_LITTLE_ENDIAN) */ - -/** Get one byte from the 4-byte address */ -#define eoe_ip4_addr1(ipaddr) (((const uint8_t *)(&(ipaddr)->addr))[0]) -#define eoe_ip4_addr2(ipaddr) (((const uint8_t *)(&(ipaddr)->addr))[1]) -#define eoe_ip4_addr3(ipaddr) (((const uint8_t *)(&(ipaddr)->addr))[2]) -#define eoe_ip4_addr4(ipaddr) (((const uint8_t *)(&(ipaddr)->addr))[3]) - -/** Set an IP address given by the four byte-parts */ -#define EOE_IP4_ADDR_TO_U32(ipaddr, a, b, c, d) \ - (ipaddr)->addr = EOE_HTONL(EOE_MAKEU32(a, b, c, d)) - -/** Header frame info 1 */ -#define EOE_HDR_FRAME_TYPE_OFFSET 0 -#define EOE_HDR_FRAME_TYPE (0xF << 0) -#define EOE_HDR_FRAME_TYPE_SET(x) (((x) & 0xF) << 0) -#define EOE_HDR_FRAME_TYPE_GET(x) (((x) >> 0) & 0xF) -#define EOE_HDR_FRAME_PORT_OFFSET 4 -#define EOE_HDR_FRAME_PORT (0xF << 4) -#define EOE_HDR_FRAME_PORT_SET(x) ((uint16)(((x) & 0xF) << 4)) -#define EOE_HDR_FRAME_PORT_GET(x) (((x) >> 4) & 0xF) -#define EOE_HDR_LAST_FRAGMENT_OFFSET 8 -#define EOE_HDR_LAST_FRAGMENT (0x1 << 8) -#define EOE_HDR_LAST_FRAGMENT_SET(x) (((x) & 0x1) << 8) -#define EOE_HDR_LAST_FRAGMENT_GET(x) (((x) >> 8) & 0x1) -#define EOE_HDR_TIME_APPEND_OFFSET 9 -#define EOE_HDR_TIME_APPEND (0x1 << 9) -#define EOE_HDR_TIME_APPEND_SET(x) (((x) & 0x1) << 9) -#define EOE_HDR_TIME_APPEND_GET(x) (((x) >> 9) & 0x1) -#define EOE_HDR_TIME_REQUEST_OFFSET 10 -#define EOE_HDR_TIME_REQUEST (0x1 << 10) -#define EOE_HDR_TIME_REQUEST_SET(x) (((x) & 0x1) << 10) -#define EOE_HDR_TIME_REQUEST_GET(x) (((x) >> 10) & 0x1) - -/** Header frame info 2 */ -#define EOE_HDR_FRAG_NO_OFFSET 0 -#define EOE_HDR_FRAG_NO (0x3F << 0) -#define EOE_HDR_FRAG_NO_SET(x) ((uint16)(((x) & 0x3F) << 0)) -#define EOE_HDR_FRAG_NO_GET(x) (((x) >> 0) & 0x3F) -#define EOE_HDR_FRAME_OFFSET_OFFSET 6 -#define EOE_HDR_FRAME_OFFSET (0x3F << 6) -#define EOE_HDR_FRAME_OFFSET_SET(x) ((uint16)(((x) & 0x3F) << 6)) -#define EOE_HDR_FRAME_OFFSET_GET(x) (((x) >> 6) & 0x3F) -#define EOE_HDR_FRAME_NO_OFFSET 12 -#define EOE_HDR_FRAME_NO (0xF << 12) -#define EOE_HDR_FRAME_NO_SET(x) ((uint16)(((x) & 0xF) << 12)) -#define EOE_HDR_FRAME_NO_GET(x) (((x) >> 12) & 0xF) - -/** EOE param */ -#define EOE_PARAM_OFFSET 4 -#define EOE_PARAM_MAC_INCLUDE (0x1 << 0) -#define EOE_PARAM_IP_INCLUDE (0x1 << 1) -#define EOE_PARAM_SUBNET_IP_INCLUDE (0x1 << 2) -#define EOE_PARAM_DEFAULT_GATEWAY_INCLUDE (0x1 << 3) -#define EOE_PARAM_DNS_IP_INCLUDE (0x1 << 4) -#define EOE_PARAM_DNS_NAME_INCLUDE (0x1 << 5) - -/** EoE frame types */ -#define EOE_FRAG_DATA 0 -#define EOE_INIT_RESP_TIMESTAMP 1 -#define EOE_INIT_REQ 2 /* Spec SET IP REQ */ -#define EOE_INIT_RESP 3 /* Spec SET IP RESP */ -#define EOE_SET_ADDR_FILTER_REQ 4 -#define EOE_SET_ADDR_FILTER_RESP 5 -#define EOE_GET_IP_PARAM_REQ 6 -#define EOE_GET_IP_PARAM_RESP 7 -#define EOE_GET_ADDR_FILTER_REQ 8 -#define EOE_GET_ADDR_FILTER_RESP 9 - -/** EoE parameter result codes */ -#define EOE_RESULT_SUCCESS 0x0000 -#define EOE_RESULT_UNSPECIFIED_ERROR 0x0001 -#define EOE_RESULT_UNSUPPORTED_FRAME_TYPE 0x0002 -#define EOE_RESULT_NO_IP_SUPPORT 0x0201 -#define EOE_RESULT_NO_DHCP_SUPPORT 0x0202 -#define EOE_RESULT_NO_FILTER_SUPPORT 0x0401 - -/** EOE ip4 address in network order */ -typedef struct eoe_ip4_addr -{ - uint32_t addr; -} eoe_ip4_addr_t; - -/** EOE ethernet address */ -OSAL_PACKED_BEGIN -typedef struct OSAL_PACKED eoe_ethaddr -{ - uint8_t addr[EOE_ETHADDR_LENGTH]; -} eoe_ethaddr_t; -OSAL_PACKED_END - -/** EoE IP request structure, storage only, no need to pack */ -typedef struct eoe_param -{ - uint8_t mac_set : 1; - uint8_t ip_set : 1; - uint8_t subnet_set : 1; - uint8_t default_gateway_set : 1; - uint8_t dns_ip_set : 1; - uint8_t dns_name_set : 1; - eoe_ethaddr_t mac; - eoe_ip4_addr_t ip; - eoe_ip4_addr_t subnet; - eoe_ip4_addr_t default_gateway; - eoe_ip4_addr_t dns_ip; - char dns_name[EOE_DNS_NAME_LENGTH]; -} eoe_param_t; - -/** EOE structure. - * Used to interpret EoE mailbox packets. - */ -OSAL_PACKED_BEGIN -typedef struct OSAL_PACKED -{ - ec_mbxheadert mbxheader; - uint16_t frameinfo1; - union - { - uint16_t frameinfo2; - uint16_t result; - }; - uint8 data[EC_MAXEOEDATA]; -} ec_EOEt; -OSAL_PACKED_END - -int ecx_EOEdefinehook(ecx_contextt *context, void *hook); -int ecx_EOEsetIp(ecx_contextt *context, - uint16 slave, - uint8 port, - eoe_param_t *ipparam, - int timeout); -int ecx_EOEgetIp(ecx_contextt *context, - uint16 slave, - uint8 port, - eoe_param_t *ipparam, - int timeout); -int ecx_EOEsend(ecx_contextt *context, - uint16 slave, - uint8 port, - int psize, - void *p, - int timeout); -int ecx_EOErecv(ecx_contextt *context, - uint16 slave, - uint8 port, - int *psize, - void *p, - int timeout); -int ecx_EOEreadfragment( - ec_mbxbuft *MbxIn, - uint8 *rxfragmentno, - uint16 *rxframesize, - uint16 *rxframeoffset, - uint16 *rxframeno, - int *psize, - void *p); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_foe.h b/dependency/x86/third_party/soem/include/soem/ec_foe.h deleted file mode 100644 index 6348b08a..00000000 --- a/dependency/x86/third_party/soem/include/soem/ec_foe.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * This software is dual-licensed under GPLv3 and a commercial - * license. See the file LICENSE.md distributed with this software for - * full license information. - */ - -/** \file - * \brief - * Headerfile for ec_foe.c - */ - -#ifndef _ec_foe_ -#define _ec_foe_ - -#ifdef __cplusplus -extern "C" { -#endif - -int ecx_FOEdefinehook(ecx_contextt *context, void *hook); -int ecx_FOEread(ecx_contextt *context, uint16 slave, char *filename, uint32 password, int *psize, void *p, int timeout); -int ecx_FOEwrite(ecx_contextt *context, uint16 slave, char *filename, uint32 password, int psize, void *p, int timeout); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_main.h b/dependency/x86/third_party/soem/include/soem/ec_main.h deleted file mode 100644 index 70189802..00000000 --- a/dependency/x86/third_party/soem/include/soem/ec_main.h +++ /dev/null @@ -1,624 +0,0 @@ -/* - * This software is dual-licensed under GPLv3 and a commercial - * license. See the file LICENSE.md distributed with this software for - * full license information. - */ - -/** \file - * \brief - * Headerfile for ec_main.c - */ - -#ifndef _ec_main_ -#define _ec_main_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "soem/ec_options.h" - -typedef struct ec_adapter ec_adaptert; -struct ec_adapter -{ - char name[EC_MAXLEN_ADAPTERNAME]; - char desc[EC_MAXLEN_ADAPTERNAME]; - ec_adaptert *next; -}; - -/** record for FMMU */ -OSAL_PACKED_BEGIN -typedef struct OSAL_PACKED ec_fmmu -{ - uint32 LogStart; - uint16 LogLength; - uint8 LogStartbit; - uint8 LogEndbit; - uint16 PhysStart; - uint8 PhysStartBit; - uint8 FMMUtype; - uint8 FMMUactive; - uint8 unused1; - uint16 unused2; -} ec_fmmut; -OSAL_PACKED_END - -/** record for sync manager */ -OSAL_PACKED_BEGIN -typedef struct OSAL_PACKED ec_sm -{ - uint16 StartAddr; - uint16 SMlength; - uint32 SMflags; -} ec_smt; -OSAL_PACKED_END - -OSAL_PACKED_BEGIN -typedef struct OSAL_PACKED ec_state_status -{ - uint16 State; - uint16 Unused; - uint16 ALstatuscode; -} ec_state_status; -OSAL_PACKED_END - -/** mailbox buffer array */ -typedef uint8 ec_mbxbuft[EC_MAXMBX + 1]; - -#define EC_MBXINENABLE (uint8 *)1 - -typedef struct -{ - int listhead, listtail, listcount; - int mbxemptylist[EC_MBXPOOLSIZE]; - osal_mutext *mbxmutex; - ec_mbxbuft mbx[EC_MBXPOOLSIZE]; -} ec_mbxpoolt; - -#define EC_MBXQUEUESTATE_NONE 0 -#define EC_MBXQUEUESTATE_REQ 1 -#define EC_MBXQUEUESTATE_FAIL 2 -#define EC_MBXQUEUESTATE_DONE 3 - -typedef struct -{ - int listhead, listtail, listcount; - ec_mbxbuft *mbx[EC_MBXPOOLSIZE]; - int mbxstate[EC_MBXPOOLSIZE]; - int mbxremove[EC_MBXPOOLSIZE]; - int mbxticket[EC_MBXPOOLSIZE]; - uint16 mbxslave[EC_MBXPOOLSIZE]; - osal_mutext *mbxmutex; -} ec_mbxqueuet; - -#define ECT_MBXPROT_AOE 0x0001 -#define ECT_MBXPROT_EOE 0x0002 -#define ECT_MBXPROT_COE 0x0004 -#define ECT_MBXPROT_FOE 0x0008 -#define ECT_MBXPROT_SOE 0x0010 -#define ECT_MBXPROT_VOE 0x0020 - -#define ECT_COEDET_SDO 0x01 -#define ECT_COEDET_SDOINFO 0x02 -#define ECT_COEDET_PDOASSIGN 0x04 -#define ECT_COEDET_PDOCONFIG 0x08 -#define ECT_COEDET_UPLOAD 0x10 -#define ECT_COEDET_SDOCA 0x20 - -#define EC_SMENABLEMASK 0xfffeffff - -typedef struct ecx_context ecx_contextt; - -#define ECT_MBXH_NONE 0 -#define ECT_MBXH_CYCLIC 1 -#define ECT_MBXH_LOST 2 - -/** Slave state - * All slave information is put in this structure. Needed for most - * user interaction with slaves. - */ -typedef struct ec_slave -{ - /** state of slave */ - uint16 state; - /** AL status code */ - uint16 ALstatuscode; - /** Configured address */ - uint16 configadr; - /** Alias address */ - uint16 aliasadr; - /** Manufacturer from EEprom */ - uint32 eep_man; - /** ID from EEprom */ - uint32 eep_id; - /** revision from EEprom */ - uint32 eep_rev; - /** serial number from EEprom */ - uint32 eep_ser; - /** Interface type */ - uint16 Itype; - /** Device type */ - uint16 Dtype; - /** output bits */ - uint16 Obits; - /** output bytes, if Obits < 8 then Obytes = 0 */ - uint32 Obytes; - /** output pointer in IOmap buffer */ - uint8 *outputs; - /** output offset in IOmap buffer */ - uint32 Ooffset; - /** startbit in first output byte */ - uint8 Ostartbit; - /** input bits */ - uint16 Ibits; - /** input bytes, if Ibits < 8 then Ibytes = 0 */ - uint32 Ibytes; - /** input pointer in IOmap buffer */ - uint8 *inputs; - /** input offset in IOmap buffer */ - uint32 Ioffset; - /** startbit in first input byte */ - uint8 Istartbit; - /** SM structure */ - ec_smt SM[EC_MAXSM]; - /** SM type 0=unused 1=MbxWr 2=MbxRd 3=Outputs 4=Inputs */ - uint8 SMtype[EC_MAXSM]; - /** FMMU structure */ - ec_fmmut FMMU[EC_MAXFMMU]; - /** FMMU0 function 0=unused 1=outputs 2=inputs 3=SM status*/ - uint8 FMMU0func; - /** FMMU1 function */ - uint8 FMMU1func; - /** FMMU2 function */ - uint8 FMMU2func; - /** FMMU3 function */ - uint8 FMMU3func; - /** length of write mailbox in bytes, if no mailbox then 0 */ - uint16 mbx_l; - /** mailbox write offset */ - uint16 mbx_wo; - /** length of read mailbox in bytes */ - uint16 mbx_rl; - /** mailbox read offset */ - uint16 mbx_ro; - /** mailbox supported protocols */ - uint16 mbx_proto; - /** Counter value of mailbox link layer protocol 1..7 */ - uint8 mbx_cnt; - /** has DC capability */ - boolean hasdc; - /** Physical type; Ebus, EtherNet combinations */ - uint8 ptype; - /** topology: 1 to 3 links */ - uint8 topology; - /** active ports bitmap : ....3210 , set if respective port is active **/ - uint8 activeports; - /** consumed ports bitmap : ....3210, used for internal delay measurement **/ - uint8 consumedports; - /** slave number for parent, 0=master */ - uint16 parent; - /** port number on parent this slave is connected to **/ - uint8 parentport; - /** port number on this slave the parent is connected to **/ - uint8 entryport; - /** DC receivetimes on port A */ - int32 DCrtA; - /** DC receivetimes on port B */ - int32 DCrtB; - /** DC receivetimes on port C */ - int32 DCrtC; - /** DC receivetimes on port D */ - int32 DCrtD; - /** propagation delay */ - int32 pdelay; - /** next DC slave */ - uint16 DCnext; - /** previous DC slave */ - uint16 DCprevious; - /** DC cycle time in ns */ - int32 DCcycle; - /** DC shift from clock modulus boundary */ - int32 DCshift; - /** DC sync activation, 0=off, 1=on */ - uint8 DCactive; - /** link to SII config */ - uint16 SIIindex; - /** 1 = 8 bytes per read, 0 = 4 bytes per read */ - uint8 eep_8byte; - /** 0 = eeprom to master , 1 = eeprom to PDI */ - uint8 eep_pdi; - /** CoE details */ - uint8 CoEdetails; - /** FoE details */ - uint8 FoEdetails; - /** EoE details */ - uint8 EoEdetails; - /** SoE details */ - uint8 SoEdetails; - /** E-bus current */ - int16 Ebuscurrent; - /** if >0 block use of LRW in processdata */ - uint8 blockLRW; - /** group */ - uint8 group; - /** first unused FMMU */ - uint8 FMMUunused; - /** Boolean for tracking whether the slave is (not) responding, not used/set by the SOEM library */ - boolean islost; - /** registered configuration function PO->SO */ - int (*PO2SOconfig)(ecx_contextt *context, uint16 slave); - /** mailbox handler state, 0 = no handler, 1 = cyclic task mbx handler, 2 = slave lost */ - int mbxhandlerstate; - /** mailbox handler robust mailbox protocol state */ - int mbxrmpstate; - /** mailbox handler RMP extended mbx in state */ - uint16 mbxinstateex; - /** pointer to CoE mailbox in buffer */ - uint8 *coembxin; - /** CoE mailbox in flag, true = mailbox full */ - boolean coembxinfull; - /** CoE mailbox in overrun counter */ - int coembxoverrun; - /** pointer to SoE mailbox in buffer */ - uint8 *soembxin; - /** SoE mailbox in flag, true = mailbox full */ - boolean soembxinfull; - /** SoE mailbox in overrun counter */ - int soembxoverrun; - /** pointer to FoE mailbox in buffer */ - uint8 *foembxin; - /** FoE mailbox in flag, true = mailbox full */ - boolean foembxinfull; - /** FoE mailbox in overrun counter */ - int foembxoverrun; - /** pointer to EoE mailbox in buffer */ - uint8 *eoembxin; - /** EoE mailbox in flag, true = mailbox full */ - boolean eoembxinfull; - /** EoE mailbox in overrun counter */ - int eoembxoverrun; - /** pointer to VoE mailbox in buffer */ - uint8 *voembxin; - /** VoE mailbox in flag, true = mailbox full */ - boolean voembxinfull; - /** VoE mailbox in overrun counter */ - int voembxoverrun; - /** pointer to AoE mailbox in buffer */ - uint8 *aoembxin; - /** AoE mailbox in flag, true = mailbox full */ - boolean aoembxinfull; - /** AoE mailbox in overrun counter */ - int aoembxoverrun; - /** pointer to out mailbox status register buffer */ - uint8 *mbxstatus; - /** readable name */ - char name[EC_MAXNAME + 1]; -} ec_slavet; - -/** for list of ethercat slave groups */ -typedef struct ec_group -{ - /** logical start address for this group */ - uint32 logstartaddr; - /** output bytes, if Obits < 8 then Obytes = 0 */ - uint32 Obytes; - /** output pointer in IOmap buffer */ - uint8 *outputs; - /** input bytes, if Ibits < 8 then Ibytes = 0 */ - uint32 Ibytes; - /** input pointer in IOmap buffer */ - uint8 *inputs; - /** has DC capability */ - boolean hasdc; - /** next DC slave */ - uint16 DCnext; - /** E-bus current */ - int16 Ebuscurrent; - /** if >0 block use of LRW in processdata */ - uint8 blockLRW; - /** IO segments used */ - uint16 nsegments; - /** 1st input segment */ - uint16 Isegment; - /** Offset in input segment */ - uint16 Ioffset; - /** Expected workcounter outputs */ - uint16 outputsWKC; - /** Expected workcounter inputs */ - uint16 inputsWKC; - /** check slave states */ - boolean docheckstate; - /** IO segmentation list. Datagrams must not break SM in two. */ - uint32 IOsegment[EC_MAXIOSEGMENTS]; - /** pointer to out mailbox status register buffer */ - uint8 *mbxstatus; - /** mailbox status register buffer length */ - int32 mbxstatuslength; - /** mailbox status lookup table */ - uint16 mbxstatuslookup[EC_MAXSLAVE]; - /** mailbox last handled in mxbhandler */ - uint16 lastmbxpos; - /** mailbox transmit queue struct */ - ec_mbxqueuet mbxtxqueue; -} ec_groupt; - -#define ECT_ESMTRANS_IP 0x0001 -#define ECT_ESMTRANS_PS 0x0002 -#define ECT_ESMTRANS_PI 0x0004 -#define ECT_ESMTRANS_SP 0x0008 -#define ECT_ESMTRANS_SO 0x0010 -#define ECT_ESMTRANS_SI 0x0020 -#define ECT_ESMTRANS_OS 0x0040 -#define ECT_ESMTRANS_OP 0x0080 -#define ECT_ESMTRANS_OI 0x0100 -#define ECT_ESMTRANS_IB 0x0200 -#define ECT_ESMTRANS_BI 0x0400 -#define ECT_ESMTRANS_II 0x0800 -#define ECT_ESMTRANS_PP 0x1000 -#define ECT_ESMTRANS_SS 0x2000 - -/** ENI CoE command structure */ -typedef struct ec_enicoecmd -{ - /** transition(s) during which command should be sent */ - uint16 Transition; - /** complete access flag */ - boolean CA; - /** ccs (1 = read, 2 = write) */ - uint8 Ccs; - /** object index */ - uint16 Index; - /** object subindex */ - uint8 SubIdx; - /** timeout in us */ - int Timeout; - /** size in bytes of parameter buffer */ - int DataSize; - /** pointer to parameter buffer */ - void *Data; -} ec_enicoecmdt; - -/** ENI slave structure */ -typedef struct ec_enislave -{ - uint16 Slave; - uint32 VendorId; - uint32 ProductCode; - uint32 RevisionNo; - ec_enicoecmdt *CoECmds; - int CoECmdCount; -} ec_enislavet; - -/** ENI structure */ -typedef struct ec_eni -{ - ec_enislavet *slave; - int slavecount; -} ec_enit; - -/** SII FMMU structure */ -typedef struct ec_eepromFMMU -{ - uint16 Startpos; - uint8 nFMMU; - uint8 FMMU0; - uint8 FMMU1; - uint8 FMMU2; - uint8 FMMU3; -} ec_eepromFMMUt; - -/** SII SM structure */ -typedef struct ec_eepromSM -{ - uint16 Startpos; - uint8 nSM; - uint16 PhStart; - uint16 Plength; - uint8 Creg; - uint8 Sreg; /* don't care */ - uint8 Activate; - uint8 PDIctrl; /* don't care */ -} ec_eepromSMt; - -/** record to store rxPDO and txPDO table from eeprom */ -typedef struct ec_eepromPDO -{ - uint16 Startpos; - uint16 Length; - uint16 nPDO; - uint16 Index[EC_MAXEEPDO]; - uint16 SyncM[EC_MAXEEPDO]; - uint16 BitSize[EC_MAXEEPDO]; - uint16 SMbitsize[EC_MAXSM]; -} ec_eepromPDOt; - -/** standard ethercat mailbox header */ -OSAL_PACKED_BEGIN -typedef struct OSAL_PACKED ec_mbxheader -{ - uint16 length; - uint16 address; - uint8 priority; - uint8 mbxtype; -} ec_mbxheadert; -OSAL_PACKED_END - -/** ALstatus and ALstatus code */ -OSAL_PACKED_BEGIN -typedef struct OSAL_PACKED ec_alstatus -{ - uint16 alstatus; - uint16 unused; - uint16 alstatuscode; -} ec_alstatust; -OSAL_PACKED_END - -/** stack structure to store segmented LRD/LWR/LRW constructs */ -typedef struct ec_idxstack -{ - uint8 pushed; - uint8 pulled; - uint8 idx[EC_MAXBUF]; - void *data[EC_MAXBUF]; - uint16 length[EC_MAXBUF]; - uint16 dcoffset[EC_MAXBUF]; - uint8 type[EC_MAXBUF]; -} ec_idxstackT; - -/** ringbuf for error storage */ -typedef struct ec_ering -{ - int16 head; - int16 tail; - ec_errort Error[EC_MAXELIST + 1]; -} ec_eringt; - -/** SyncManager Communication Type structure for CA */ -OSAL_PACKED_BEGIN -typedef struct OSAL_PACKED ec_SMcommtype -{ - uint8 n; - uint8 nu1; - uint8 SMtype[EC_MAXSM]; -} ec_SMcommtypet; -OSAL_PACKED_END - -/** SDO assign structure for CA */ -OSAL_PACKED_BEGIN -typedef struct OSAL_PACKED ec_PDOassign -{ - uint8 n; - uint8 nu1; - uint16 index[256]; -} ec_PDOassignt; -OSAL_PACKED_END - -/** SDO description structure for CA */ -OSAL_PACKED_BEGIN -typedef struct OSAL_PACKED ec_PDOdesc -{ - uint8 n; - uint8 nu1; - uint32 PDO[256]; -} ec_PDOdesct; -OSAL_PACKED_END - -/** Context structure, referenced by all ecx functions*/ -struct ecx_context -{ - /** @publicsection */ - /* Network state */ - - /** port, may include red_port */ - ecx_portt port; - /** list of detected slaves */ - ec_slavet slavelist[EC_MAXSLAVE]; - /** number of slaves found in configuration */ - int slavecount; - /** list of groups */ - ec_groupt grouplist[EC_MAXGROUP]; - /** ecaterror state */ - boolean ecaterror; - /** last DC time from slaves */ - int64 DCtime; - - /** @privatesection */ - /* Internal state */ - - /** internal, eeprom cache buffer */ - uint8 esibuf[EC_MAXEEPBUF]; - /** internal, eeprom cache map */ - uint32 esimap[EC_MAXEEPBITMAP]; - /** internal, current slave for eeprom cache */ - uint16 esislave; - /** internal, error list */ - ec_eringt elist; - /** internal, processdata stack buffer info */ - ec_idxstackT idxstack; - /** internal, SM buffer */ - ec_SMcommtypet SMcommtype[EC_MAX_MAPT]; - /** internal, PDO assign list */ - ec_PDOassignt PDOassign[EC_MAX_MAPT]; - /** internal, PDO description list */ - ec_PDOdesct PDOdesc[EC_MAX_MAPT]; - /** internal, SM list from eeprom */ - ec_eepromSMt eepSM; - /** internal, FMMU list from eeprom */ - ec_eepromFMMUt eepFMMU; - /** internal, mailbox pool */ - ec_mbxpoolt mbxpool; - - /** @publicsection */ - /* Configurable settings */ - - /** network information hook */ - ec_enit *ENI; - /** registered FoE hook */ - int (*FOEhook)(uint16 slave, int packetnumber, int datasize); - /** registered EoE hook */ - int (*EOEhook)(ecx_contextt *context, uint16 slave, void *eoembx); - /** flag to control legacy automatic state change or manual state change */ - int manualstatechange; - /** opaque pointer to application userdata, never used by SOEM. */ - void *userdata; - /** In overlapped mode, inputs will replace outputs in the incoming - * frame. Use this mode for TI ESC:s. Processdata is always aligned - * on a byte boundary. */ - boolean overlappedMode; - /** Do not map each slave on a byte boundary. May result in smaller - * frame sizes. Has no effect in overlapped mode. */ - boolean packedMode; -}; - -ec_adaptert *ec_find_adapters(void); -void ec_free_adapters(ec_adaptert *adapter); -uint8 ec_nextmbxcnt(uint8 cnt); -void ec_clearmbx(ec_mbxbuft *Mbx); -void ecx_pusherror(ecx_contextt *context, const ec_errort *Ec); -boolean ecx_poperror(ecx_contextt *context, ec_errort *Ec); -boolean ecx_iserror(ecx_contextt *context); -void ecx_packeterror(ecx_contextt *context, uint16 Slave, uint16 Index, uint8 SubIdx, uint16 ErrorCode); -int ecx_init(ecx_contextt *context, const char *ifname); -int ecx_init_redundant(ecx_contextt *context, ecx_redportt *redport, const char *ifname, char *if2name); -void ecx_close(ecx_contextt *context); -uint8 ecx_siigetbyte(ecx_contextt *context, uint16 slave, uint16 address); -int16 ecx_siifind(ecx_contextt *context, uint16 slave, uint16 cat); -void ecx_siistring(ecx_contextt *context, char *str, uint16 slave, uint16 Sn); -uint16 ecx_siiFMMU(ecx_contextt *context, uint16 slave, ec_eepromFMMUt *FMMU); -uint16 ecx_siiSM(ecx_contextt *context, uint16 slave, ec_eepromSMt *SM); -uint16 ecx_siiSMnext(ecx_contextt *context, uint16 slave, ec_eepromSMt *SM, uint16 n); -uint32 ecx_siiPDO(ecx_contextt *context, uint16 slave, ec_eepromPDOt *PDO, uint8 t); -int ecx_readstate(ecx_contextt *context); -int ecx_writestate(ecx_contextt *context, uint16 slave); -uint16 ecx_statecheck(ecx_contextt *context, uint16 slave, uint16 reqstate, int timeout); -int ecx_mbxhandler(ecx_contextt *context, uint8 group, int limit); -int ecx_mbxempty(ecx_contextt *context, uint16 slave, int timeout); -int ecx_mbxsend(ecx_contextt *context, uint16 slave, ec_mbxbuft *mbx, int timeout); -int ecx_mbxreceive(ecx_contextt *context, uint16 slave, ec_mbxbuft **mbx, int timeout); -int ecx_mbxENIinitcmds(ecx_contextt *context, uint16 slave, uint16_t transition); -void ecx_esidump(ecx_contextt *context, uint16 slave, uint8 *esibuf); -uint32 ecx_readeeprom(ecx_contextt *context, uint16 slave, uint16 eeproma, int timeout); -int ecx_writeeeprom(ecx_contextt *context, uint16 slave, uint16 eeproma, uint16 data, int timeout); -int ecx_eeprom2master(ecx_contextt *context, uint16 slave); -int ecx_eeprom2pdi(ecx_contextt *context, uint16 slave); -uint64 ecx_readeepromAP(ecx_contextt *context, uint16 aiadr, uint16 eeproma, int timeout); -int ecx_writeeepromAP(ecx_contextt *context, uint16 aiadr, uint16 eeproma, uint16 data, int timeout); -uint64 ecx_readeepromFP(ecx_contextt *context, uint16 configadr, uint16 eeproma, int timeout); -int ecx_writeeepromFP(ecx_contextt *context, uint16 configadr, uint16 eeproma, uint16 data, int timeout); -void ecx_readeeprom1(ecx_contextt *context, uint16 slave, uint16 eeproma); -uint32 ecx_readeeprom2(ecx_contextt *context, uint16 slave, int timeout); -int ecx_receive_processdata_group(ecx_contextt *context, uint8 group, int timeout); -int ecx_send_processdata(ecx_contextt *context); -int ecx_receive_processdata(ecx_contextt *context, int timeout); -int ecx_send_processdata_group(ecx_contextt *context, uint8 group); -ec_mbxbuft *ecx_getmbx(ecx_contextt *context); -int ecx_dropmbx(ecx_contextt *context, ec_mbxbuft *mbx); -int ecx_initmbxpool(ecx_contextt *context); -int ecx_initmbxqueue(ecx_contextt *context, uint8 group); -int ecx_slavembxcyclic(ecx_contextt *context, uint16 slave); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_options.h b/dependency/x86/third_party/soem/include/soem/ec_options.h deleted file mode 100644 index a6b3d7f9..00000000 --- a/dependency/x86/third_party/soem/include/soem/ec_options.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * This software is dual-licensed under GPLv3 and a commercial - * license. See the file LICENSE.md distributed with this software for - * full license information. - */ - -/** \file - * \brief - * Build options for SOEM - */ - -#ifndef _ec_options_ -#define _ec_options_ - -#ifdef __cplusplus -extern "C" { -#endif - -/* Max sizes */ - -/** standard frame buffer size in bytes */ -#define EC_BUFSIZE (EC_MAXECATFRAME) - -/** number of frame buffers per channel (tx, rx1 rx2) */ -#define EC_MAXBUF (16) - -/** size of EEPROM bitmap cache */ -#define EC_MAXEEPBITMAP (128) - -/** size of EEPROM cache buffer */ -#define EC_MAXEEPBUF (EC_MAXEEPBITMAP << 5) - -/** default group size in 2^x */ -#define EC_LOGGROUPOFFSET (16) - -/** max. entries in EtherCAT error list */ -#define EC_MAXELIST (64) - -/** max. length of readable name in slavelist and Object Description List */ -#define EC_MAXNAME (40) - -/** max. number of slaves in array */ -#define EC_MAXSLAVE (200) - -/** max. number of groups */ -#define EC_MAXGROUP (2) - -/** max. number of IO segments per group */ -#define EC_MAXIOSEGMENTS (64) - -/** max. mailbox size */ -#define EC_MAXMBX (1486) - -/** number of mailboxes in pool */ -#define EC_MBXPOOLSIZE (32) - -/** max. eeprom PDO entries */ -#define EC_MAXEEPDO (0x200) - -/** max. SM used */ -#define EC_MAXSM (8) - -/** max. FMMU used */ -#define EC_MAXFMMU (4) - -/** max. adapter name length */ -#define EC_MAXLEN_ADAPTERNAME (128) - -/** define maximum number of concurrent threads in mapping */ -#define EC_MAX_MAPT (1) - -/** max entries in Object Description list */ -#define EC_MAXODLIST (1024) - -/** max entries in Object Entry list */ -#define EC_MAXOELIST (256) - -/** max. length of readable SoE name */ -#define EC_SOE_MAXNAME (60) - -/** max. number of SoE mappings */ -#define EC_SOE_MAXMAPPING (64) - -/* Timeouts and retries */ - -/** timeout value in us for tx frame to return to rx */ -#define EC_TIMEOUTRET (2000) - -/** timeout value in us for safe data transfer, max. triple retry */ -#define EC_TIMEOUTRET3 (EC_TIMEOUTRET * 3) - -/** timeout value in us for return "safe" variant (f.e. wireless) */ -#define EC_TIMEOUTSAFE (20000) - -/** timeout value in us for EEPROM access */ -#define EC_TIMEOUTEEP (20000) - -/** timeout value in us for tx mailbox cycle */ -#define EC_TIMEOUTTXM (20000) - -/** timeout value in us for rx mailbox cycle */ -#define EC_TIMEOUTRXM (700000) - -/** timeout value in us for check statechange */ -#define EC_TIMEOUTSTATE (2000000) - -/** default number of retries if wkc <= 0 */ -#define EC_DEFAULTRETRIES (3) - -/* MAC addresses */ - -/** Primary source MAC address used for EtherCAT. - * - * This address is not the MAC address used from the NIC. EtherCAT - * does not care about MAC addressing, but it is used here to - * differentiate the route the packet traverses through the EtherCAT - * segment. This is needed to find out the packet flow in redundant - * configurations. */ -#define EC_PRIMARY_MAC_ARRAY {0x0101, 0x0101, 0x0101} - -/** Secondary source MAC address used for EtherCAT. */ -#define EC_SECONDARY_MAC_ARRAY {0x0404, 0x0404, 0x0404} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_print.h b/dependency/x86/third_party/soem/include/soem/ec_print.h deleted file mode 100644 index 920f3dac..00000000 --- a/dependency/x86/third_party/soem/include/soem/ec_print.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * This software is dual-licensed under GPLv3 and a commercial - * license. See the file LICENSE.md distributed with this software for - * full license information. - */ - -/** \file - * \brief - * Headerfile for ec_print.c - */ - -#ifndef _ec_print_ -#define _ec_print_ - -#ifdef __cplusplus -extern "C" { -#endif - -const char *ec_sdoerror2string(uint32 sdoerrorcode); -char *ec_ALstatuscode2string(uint16 ALstatuscode); -char *ec_soeerror2string(uint16 errorcode); -char *ec_mbxerror2string(uint16 errorcode); -char *ecx_err2string(const ec_errort Ec); -char *ecx_elist2string(ecx_contextt *context); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_soe.h b/dependency/x86/third_party/soem/include/soem/ec_soe.h deleted file mode 100644 index e6dbf3ee..00000000 --- a/dependency/x86/third_party/soem/include/soem/ec_soe.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - * This software is dual-licensed under GPLv3 and a commercial - * license. See the file LICENSE.md distributed with this software for - * full license information. - */ - -/** \file - * \brief - * Headerfile for ec_soe.c - */ - -#ifndef _ec_soe_ -#define _ec_soe_ - -#ifdef __cplusplus -extern "C" { -#endif - -#define EC_SOE_DATASTATE_B 0x01 -#define EC_SOE_NAME_B 0x02 -#define EC_SOE_ATTRIBUTE_B 0x04 -#define EC_SOE_UNIT_B 0x08 -#define EC_SOE_MIN_B 0x10 -#define EC_SOE_MAX_B 0x20 -#define EC_SOE_VALUE_B 0x40 -#define EC_SOE_DEFAULT_B 0x80 - -#define EC_IDN_MDTCONFIG 24 -#define EC_IDN_ATCONFIG 16 - -/** SoE name structure */ -OSAL_PACKED_BEGIN -typedef struct OSAL_PACKED -{ - /** current length in bytes of list */ - uint16 currentlength; - /** maximum length in bytes of list */ - uint16 maxlength; - char name[EC_SOE_MAXNAME]; -} ec_SoEnamet; -OSAL_PACKED_END - -/** SoE list structure */ -OSAL_PACKED_BEGIN -typedef struct OSAL_PACKED -{ - /** current length in bytes of list */ - uint16 currentlength; - /** maximum length in bytes of list */ - uint16 maxlength; - union - { - uint8 byte[8]; - uint16 word[4]; - uint32 dword[2]; - uint64 lword[1]; - }; -} ec_SoElistt; -OSAL_PACKED_END - -/** SoE IDN mapping structure */ -OSAL_PACKED_BEGIN -typedef struct OSAL_PACKED -{ - /** current length in bytes of list */ - uint16 currentlength; - /** maximum length in bytes of list */ - uint16 maxlength; - uint16 idn[EC_SOE_MAXMAPPING]; -} ec_SoEmappingt; -OSAL_PACKED_END - -#define EC_SOE_LENGTH_1 0x00 -#define EC_SOE_LENGTH_2 0x01 -#define EC_SOE_LENGTH_4 0x02 -#define EC_SOE_LENGTH_8 0x03 -#define EC_SOE_TYPE_BINARY 0x00 -#define EC_SOE_TYPE_UINT 0x01 -#define EC_SOE_TYPE_INT 0x02 -#define EC_SOE_TYPE_HEX 0x03 -#define EC_SOE_TYPE_STRING 0x04 -#define EC_SOE_TYPE_IDN 0x05 -#define EC_SOE_TYPE_FLOAT 0x06 -#define EC_SOE_TYPE_PARAMETER 0x07 - -/** SoE attribute structure */ -OSAL_PACKED_BEGIN -typedef struct OSAL_PACKED -{ - /** evaluation factor for display purposes */ - uint32 evafactor : 16; - /** length of IDN element(s) */ - uint32 length : 2; - /** IDN is list */ - uint32 list : 1; - /** IDN is command */ - uint32 command : 1; - /** datatype */ - uint32 datatype : 3; - uint32 reserved1 : 1; - /** decimals to display if float datatype */ - uint32 decimals : 4; - /** write protected in pre-op */ - uint32 wppreop : 1; - /** write protected in safe-op */ - uint32 wpsafeop : 1; - /** write protected in op */ - uint32 wpop : 1; - uint32 reserved2 : 1; -} ec_SoEattributet; -OSAL_PACKED_END - -int ecx_SoEread(ecx_contextt *context, uint16 slave, uint8 driveNo, uint8 elementflags, uint16 idn, int *psize, void *p, int timeout); -int ecx_SoEwrite(ecx_contextt *context, uint16 slave, uint8 driveNo, uint8 elementflags, uint16 idn, int psize, void *p, int timeout); -int ecx_readIDNmap(ecx_contextt *context, uint16 slave, uint32 *Osize, uint32 *Isize); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/dependency/x86/third_party/soem/include/soem/ec_type.h b/dependency/x86/third_party/soem/include/soem/ec_type.h deleted file mode 100644 index faca1707..00000000 --- a/dependency/x86/third_party/soem/include/soem/ec_type.h +++ /dev/null @@ -1,551 +0,0 @@ -/* - * This software is dual-licensed under GPLv3 and a commercial - * license. See the file LICENSE.md distributed with this software for - * full license information. - */ - -/** \file - * \brief - * General typedefs and defines for EtherCAT. - * - * Defines that could need optimisation for specific applications - * are the EC_TIMEOUTxxx. Assumptions for the standard settings are a - * standard linux PC or laptop and a wired connection to maximal 100 slaves. - * For use with wireless connections or lots of slaves the timeouts need - * increasing. For fast systems running Xenomai and RT-net or alike the - * timeouts need to be shorter. - */ - -#ifndef _EC_TYPE_H -#define _EC_TYPE_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "osal.h" -#include "soem/ec_options.h" - -/* Endianness */ - -/** Define little endian target by default if no endian is set */ -#if !defined(EC_LITTLE_ENDIAN) && !defined(EC_BIG_ENDIAN) -#define EC_LITTLE_ENDIAN -#endif - -/* Error codes */ - -/** return value no frame returned */ -#define EC_NOFRAME -1 -/** return value unknown frame received */ -#define EC_OTHERFRAME -2 -/** return value general error */ -#define EC_ERROR -3 -/** return value too many slaves */ -#define EC_SLAVECOUNTEXCEEDED -4 -/** return value request timeout */ -#define EC_TIMEOUT -5 - -/* Constants */ - -/** maximum EtherCAT frame length in bytes */ -#define EC_MAXECATFRAME 1518 -/** size of DC datagram used in first LRW frame */ -#define EC_FIRSTDCDATAGRAM 20 -/** datagram type EtherCAT */ -#define EC_ECATTYPE 0x1000 -/** maximum EtherCAT LRW frame length in bytes */ -/* MTU - Ethernet header - length - datagram header - WCK - FCS */ -#define EC_MAXLRWDATA (EC_MAXECATFRAME - 14 - 2 - 10 - 2 - 4) - -/** definition for frame buffers */ -typedef uint8 ec_bufT[EC_BUFSIZE]; - -/** ethernet header definition */ -OSAL_PACKED_BEGIN -typedef struct OSAL_PACKED -{ - /** destination MAC */ - uint16 da0, da1, da2; - /** source MAC */ - uint16 sa0, sa1, sa2; - /** ethernet type */ - uint16 etype; -} ec_etherheadert; -OSAL_PACKED_END - -/** ethernet header size */ -#define ETH_HEADERSIZE sizeof(ec_etherheadert) - -/** EtherCAT datagram header definition */ -OSAL_PACKED_BEGIN -typedef struct OSAL_PACKED -{ - /** length of EtherCAT datagram */ - uint16 elength; - /** EtherCAT command, see ec_cmdtype */ - uint8 command; - /** index, used in SOEM for Tx to Rx recombination */ - uint8 index; - /** ADP */ - uint16 ADP; - /** ADO */ - uint16 ADO; - /** length of data portion in datagram */ - uint16 dlength; - /** interrupt, currently unused */ - uint16 irpt; -} ec_comt; -OSAL_PACKED_END - -/** EtherCAT header size */ -#define EC_HEADERSIZE sizeof(ec_comt) -/** size of ec_comt.elength item in EtherCAT header */ -#define EC_ELENGTHSIZE sizeof(uint16) -/** offset position of command in EtherCAT header */ -#define EC_CMDOFFSET EC_ELENGTHSIZE -/** size of workcounter item in EtherCAT datagram */ -#define EC_WKCSIZE sizeof(uint16) -/** definition of datagram follows bit in ec_comt.dlength */ -#define EC_DATAGRAMFOLLOWS (1 << 15) - -/** Possible error codes returned. */ -typedef enum -{ - /** No error */ - EC_ERR_OK = 0, - /** Library already initialized. */ - EC_ERR_ALREADY_INITIALIZED, - /** Library not initialized. */ - EC_ERR_NOT_INITIALIZED, - /** Timeout occurred during execution of the function. */ - EC_ERR_TIMEOUT, - /** No slaves were found. */ - EC_ERR_NO_SLAVES, - /** Function failed. */ - EC_ERR_NOK -} ec_err; - -/** Possible EtherCAT slave states */ -typedef enum -{ - /** No valid state. */ - EC_STATE_NONE = 0x00, - /** Init state*/ - EC_STATE_INIT = 0x01, - /** Pre-operational. */ - EC_STATE_PRE_OP = 0x02, - /** Boot state*/ - EC_STATE_BOOT = 0x03, - /** Safe-operational. */ - EC_STATE_SAFE_OP = 0x04, - /** Operational */ - EC_STATE_OPERATIONAL = 0x08, - /** Error or ACK error */ - EC_STATE_ACK = 0x10, - EC_STATE_ERROR = 0x10 -} ec_state; - -/** Possible buffer states */ -typedef enum -{ - /** Empty */ - EC_BUF_EMPTY = 0x00, - /** Allocated, but not filled */ - EC_BUF_ALLOC = 0x01, - /** Transmitted */ - EC_BUF_TX = 0x02, - /** Received, but not consumed */ - EC_BUF_RCVD = 0x03, - /** Cycle completed */ - EC_BUF_COMPLETE = 0x04 -} ec_bufstate; - -/** Ethercat data types */ -typedef enum -{ - ECT_BOOLEAN = 0x0001, - ECT_INTEGER8 = 0x0002, - ECT_INTEGER16 = 0x0003, - ECT_INTEGER32 = 0x0004, - ECT_UNSIGNED8 = 0x0005, - ECT_UNSIGNED16 = 0x0006, - ECT_UNSIGNED32 = 0x0007, - ECT_REAL32 = 0x0008, - ECT_VISIBLE_STRING = 0x0009, - ECT_OCTET_STRING = 0x000A, - ECT_UNICODE_STRING = 0x000B, - ECT_TIME_OF_DAY = 0x000C, - ECT_TIME_DIFFERENCE = 0x000D, - ECT_DOMAIN = 0x000F, - ECT_INTEGER24 = 0x0010, - ECT_REAL64 = 0x0011, - ECT_INTEGER64 = 0x0015, - ECT_UNSIGNED24 = 0x0016, - ECT_UNSIGNED64 = 0x001B, - ECT_BIT1 = 0x0030, - ECT_BIT2 = 0x0031, - ECT_BIT3 = 0x0032, - ECT_BIT4 = 0x0033, - ECT_BIT5 = 0x0034, - ECT_BIT6 = 0x0035, - ECT_BIT7 = 0x0036, - ECT_BIT8 = 0x0037 -} ec_datatype; - -/** Ethercat command types */ -typedef enum -{ - /** No operation */ - EC_CMD_NOP = 0x00, - /** Auto Increment Read */ - EC_CMD_APRD, - /** Auto Increment Write */ - EC_CMD_APWR, - /** Auto Increment Read Write */ - EC_CMD_APRW, - /** Configured Address Read */ - EC_CMD_FPRD, - /** Configured Address Write */ - EC_CMD_FPWR, - /** Configured Address Read Write */ - EC_CMD_FPRW, - /** Broadcast Read */ - EC_CMD_BRD, - /** Broadcast Write */ - EC_CMD_BWR, - /** Broadcast Read Write */ - EC_CMD_BRW, - /** Logical Memory Read */ - EC_CMD_LRD, - /** Logical Memory Write */ - EC_CMD_LWR, - /** Logical Memory Read Write */ - EC_CMD_LRW, - /** Auto Increment Read Multiple Write */ - EC_CMD_ARMW, - /** Configured Read Multiple Write */ - EC_CMD_FRMW - /** Reserved */ -} ec_cmdtype; - -/** Ethercat EEprom command types */ -typedef enum -{ - /** No operation */ - EC_ECMD_NOP = 0x0000, - /** Read */ - EC_ECMD_READ = 0x0100, - /** Write */ - EC_ECMD_WRITE = 0x0201, - /** Reload */ - EC_ECMD_RELOAD = 0x0300 -} ec_ecmdtype; - -/** EEprom state machine read size */ -#define EC_ESTAT_R64 0x0040 -/** EEprom state machine busy flag */ -#define EC_ESTAT_BUSY 0x8000 -/** EEprom state machine error flag mask */ -#define EC_ESTAT_EMASK 0x7800 -/** EEprom state machine error acknowledge */ -#define EC_ESTAT_NACK 0x2000 - -/* Ethercat SSI (Slave Information Interface) */ - -/** Start address SII sections in Eeprom */ -#define ECT_SII_START 0x0040 - -enum -{ - /** SII category strings */ - ECT_SII_STRING = 10, - /** SII category general */ - ECT_SII_GENERAL = 30, - /** SII category FMMU */ - ECT_SII_FMMU = 40, - /** SII category SM */ - ECT_SII_SM = 41, - /** SII category PDO */ - ECT_SII_PDO = 50 -}; - -/** Item offsets in SII general section */ -enum -{ - ECT_SII_MANUF = 0x0008, - ECT_SII_ID = 0x000a, - ECT_SII_REV = 0x000c, - ECT_SII_SER = 0x000e, - ECT_SII_BOOTRXMBX = 0x0014, - ECT_SII_BOOTTXMBX = 0x0016, - ECT_SII_MBXSIZE = 0x0019, - ECT_SII_TXMBXADR = 0x001a, - ECT_SII_RXMBXADR = 0x0018, - ECT_SII_MBXPROTO = 0x001c -}; - -/** Mailbox types definitions */ -enum -{ - /** Error mailbox type */ - ECT_MBXT_ERR = 0x00, - /** ADS over EtherCAT mailbox type */ - ECT_MBXT_AOE, - /** Ethernet over EtherCAT mailbox type */ - ECT_MBXT_EOE, - /** CANopen over EtherCAT mailbox type */ - ECT_MBXT_COE, - /** File over EtherCAT mailbox type */ - ECT_MBXT_FOE, - /** Servo over EtherCAT mailbox type */ - ECT_MBXT_SOE, - /** Vendor over EtherCAT mailbox type */ - ECT_MBXT_VOE = 0x0f -}; - -/** CoE mailbox types */ -enum -{ - ECT_COES_EMERGENCY = 0x01, - ECT_COES_SDOREQ, - ECT_COES_SDORES, - ECT_COES_TXPDO, - ECT_COES_RXPDO, - ECT_COES_TXPDO_RR, - ECT_COES_RXPDO_RR, - ECT_COES_SDOINFO -}; - -/** CoE SDO commands */ -enum -{ - ECT_SDO_DOWN_INIT = 0x21, - ECT_SDO_DOWN_EXP = 0x23, - ECT_SDO_DOWN_INIT_CA = 0x31, - ECT_SDO_UP_REQ = 0x40, - ECT_SDO_UP_REQ_CA = 0x50, - ECT_SDO_SEG_UP_REQ = 0x60, - ECT_SDO_ABORT = 0x80 -}; - -/** CoE Object Description commands */ -enum -{ - ECT_GET_ODLIST_REQ = 0x01, - ECT_GET_ODLIST_RES = 0x02, - ECT_GET_OD_REQ = 0x03, - ECT_GET_OD_RES = 0x04, - ECT_GET_OE_REQ = 0x05, - ECT_GET_OE_RES = 0x06, - ECT_SDOINFO_ERROR = 0x07 -}; - -/** FoE opcodes */ -enum -{ - ECT_FOE_READ = 0x01, - ECT_FOE_WRITE, - ECT_FOE_DATA, - ECT_FOE_ACK, - ECT_FOE_ERROR, - ECT_FOE_BUSY -}; - -/** SoE opcodes */ -enum -{ - ECT_SOE_READREQ = 0x01, - ECT_SOE_READRES, - ECT_SOE_WRITEREQ, - ECT_SOE_WRITERES, - ECT_SOE_NOTIFICATION, - ECT_SOE_EMERGENCY -}; - -/** Ethercat registers */ -enum -{ - ECT_REG_TYPE = 0x0000, - ECT_REG_PORTDES = 0x0007, - ECT_REG_ESCSUP = 0x0008, - ECT_REG_STADR = 0x0010, - ECT_REG_ALIAS = 0x0012, - ECT_REG_DLCTL = 0x0100, - ECT_REG_DLPORT = 0x0101, - ECT_REG_DLALIAS = 0x0103, - ECT_REG_DLSTAT = 0x0110, - ECT_REG_ALCTL = 0x0120, - ECT_REG_ALSTAT = 0x0130, - ECT_REG_ALSTATCODE = 0x0134, - ECT_REG_PDICTL = 0x0140, - ECT_REG_IRQMASK = 0x0200, - ECT_REG_RXERR = 0x0300, - ECT_REG_FRXERR = 0x0308, - ECT_REG_EPUECNT = 0x030C, - ECT_REG_PECNT = 0x030D, - ECT_REG_PECODE = 0x030E, - ECT_REG_LLCNT = 0x0310, - ECT_REG_WDCNT = 0x0442, - ECT_REG_EEPCFG = 0x0500, - ECT_REG_EEPCTL = 0x0502, - ECT_REG_EEPSTAT = 0x0502, - ECT_REG_EEPADR = 0x0504, - ECT_REG_EEPDAT = 0x0508, - ECT_REG_FMMU0 = 0x0600, - ECT_REG_FMMU1 = ECT_REG_FMMU0 + 0x10, - ECT_REG_FMMU2 = ECT_REG_FMMU1 + 0x10, - ECT_REG_FMMU3 = ECT_REG_FMMU2 + 0x10, - ECT_REG_SM0 = 0x0800, - ECT_REG_SM1 = ECT_REG_SM0 + 0x08, - ECT_REG_SM2 = ECT_REG_SM1 + 0x08, - ECT_REG_SM3 = ECT_REG_SM2 + 0x08, - ECT_REG_SM0STAT = ECT_REG_SM0 + 0x05, - ECT_REG_SM1STAT = ECT_REG_SM1 + 0x05, - ECT_REG_SM1ACT = ECT_REG_SM1 + 0x06, - ECT_REG_SM1CONTR = ECT_REG_SM1 + 0x07, - ECT_REG_DCTIME0 = 0x0900, - ECT_REG_DCTIME1 = 0x0904, - ECT_REG_DCTIME2 = 0x0908, - ECT_REG_DCTIME3 = 0x090C, - ECT_REG_DCSYSTIME = 0x0910, - ECT_REG_DCSOF = 0x0918, - ECT_REG_DCSYSOFFSET = 0x0920, - ECT_REG_DCSYSDELAY = 0x0928, - ECT_REG_DCSYSDIFF = 0x092C, - ECT_REG_DCSPEEDCNT = 0x0930, - ECT_REG_DCTIMEFILT = 0x0934, - ECT_REG_DCCUC = 0x0980, - ECT_REG_DCSYNCACT = 0x0981, - ECT_REG_DCSTART0 = 0x0990, - ECT_REG_DCCYCLE0 = 0x09A0, - ECT_REG_DCCYCLE1 = 0x09A4 -}; - -/** standard SDO Sync Manager Communication Type */ -#define ECT_SDO_SMCOMMTYPE 0x1c00 -/** standard SDO PDO assignment */ -#define ECT_SDO_PDOASSIGN 0x1c10 -/** standard SDO RxPDO assignment */ -#define ECT_SDO_RXPDOASSIGN 0x1c12 -/** standard SDO TxPDO assignment */ -#define ECT_SDO_TXPDOASSIGN 0x1c13 - -/** Ethercat packet type */ -#define ETH_P_ECAT 0x88A4 - -/** Error types */ -typedef enum -{ - EC_ERR_TYPE_SDO_ERROR = 0, - EC_ERR_TYPE_EMERGENCY = 1, - EC_ERR_TYPE_PACKET_ERROR = 3, - EC_ERR_TYPE_SDOINFO_ERROR = 4, - EC_ERR_TYPE_FOE_ERROR = 5, - EC_ERR_TYPE_FOE_BUF2SMALL = 6, - EC_ERR_TYPE_FOE_PACKETNUMBER = 7, - EC_ERR_TYPE_SOE_ERROR = 8, - EC_ERR_TYPE_MBX_ERROR = 9, - EC_ERR_TYPE_FOE_FILE_NOTFOUND = 10, - EC_ERR_TYPE_EOE_INVALID_RX_DATA = 11 -} ec_err_type; - -/** Struct to retrieve errors. */ -typedef struct -{ - /** Time at which the error was generated. */ - ec_timet Time; - /** Signal bit, error set but not read */ - boolean Signal; - /** Slave number that generated the error */ - uint16 Slave; - /** CoE SDO index that generated the error */ - uint16 Index; - /** CoE SDO subindex that generated the error */ - uint8 SubIdx; - /** Type of error */ - ec_err_type Etype; - union - { - /** General abortcode */ - int32 AbortCode; - /** Specific error for Emergency mailbox */ - struct - { - uint16 ErrorCode; - uint8 ErrorReg; - uint8 b1; - uint16 w1; - uint16 w2; - }; - }; -} ec_errort; - -/** Helper macros */ - -/** Set the count value in the Mailbox header */ -#define MBX_HDR_SET_CNT(cnt) ((uint8)((cnt) << 4)) - -/** Macro to make a word from 2 bytes */ -#define MK_WORD(msb, lsb) ((((uint16)(msb)) << 8) | (lsb)) -/** Macro to get hi byte of a word */ -#define HI_BYTE(w) ((w) >> 8) -/** Macro to get low byte of a word */ -#define LO_BYTE(w) ((w) & 0x00ff) -/** Macro to swap hi and low byte of a word */ -#define SWAP(w) ((((w) & 0xff00) >> 8) | (((w) & 0x00ff) << 8)) -/** Macro to get hi word of a dword */ -#define LO_WORD(l) ((l) & 0xffff) -/** Macro to get hi word of a dword */ -#define HI_WORD(l) ((l) >> 16) - -#define get_unaligned(ptr) \ - ({ __typeof__(*(ptr)) __tmp; memcpy(&__tmp, (ptr), sizeof(*(ptr))); __tmp; }) - -#define put_unaligned32(val, ptr) \ - (memcpy((ptr), &(val), 4)) - -#define put_unaligned64(val, ptr) \ - (memcpy((ptr), &(val), 8)) - -#if !defined(EC_BIG_ENDIAN) && defined(EC_LITTLE_ENDIAN) - -#define htoes(A) (A) -#define htoel(A) (A) -#define htoell(A) (A) -#define etohs(A) (A) -#define etohl(A) (A) -#define etohll(A) (A) - -#elif !defined(EC_LITTLE_ENDIAN) && defined(EC_BIG_ENDIAN) - -#define htoes(A) ((((uint16)(A) & 0xff00) >> 8) | \ - (((uint16)(A) & 0x00ff) << 8)) -#define htoel(A) ((((uint32)(A) & 0xff000000) >> 24) | \ - (((uint32)(A) & 0x00ff0000) >> 8) | \ - (((uint32)(A) & 0x0000ff00) << 8) | \ - (((uint32)(A) & 0x000000ff) << 24)) -#define htoell(A) ((((uint64)(A) & (uint64)0xff00000000000000ULL) >> 56) | \ - (((uint64)(A) & (uint64)0x00ff000000000000ULL) >> 40) | \ - (((uint64)(A) & (uint64)0x0000ff0000000000ULL) >> 24) | \ - (((uint64)(A) & (uint64)0x000000ff00000000ULL) >> 8) | \ - (((uint64)(A) & (uint64)0x00000000ff000000ULL) << 8) | \ - (((uint64)(A) & (uint64)0x0000000000ff0000ULL) << 24) | \ - (((uint64)(A) & (uint64)0x000000000000ff00ULL) << 40) | \ - (((uint64)(A) & (uint64)0x00000000000000ffULL) << 56)) - -#define etohs htoes -#define etohl htoel -#define etohll htoell - -#else - -#error "Must define one of EC_BIG_ENDIAN or EC_LITTLE_ENDIAN" - -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* _EC_TYPE_H */ diff --git a/dependency/x86/third_party/soem/include/soem/nicdrv.h b/dependency/x86/third_party/soem/include/soem/nicdrv.h deleted file mode 100644 index 010d8efc..00000000 --- a/dependency/x86/third_party/soem/include/soem/nicdrv.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - * This software is dual-licensed under GPLv3 and a commercial - * license. See the file LICENSE.md distributed with this software for - * full license information. - */ - -/** \file - * \brief - * Headerfile for nicdrv.c - */ - -#ifndef _nicdrvh_ -#define _nicdrvh_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/** pointer structure to Tx and Rx stacks */ -typedef struct -{ - /** socket connection used */ - int *sock; - /** tx buffer */ - ec_bufT (*txbuf)[EC_MAXBUF]; - /** tx buffer lengths */ - int (*txbuflength)[EC_MAXBUF]; - /** temporary receive buffer */ - ec_bufT *tempbuf; - /** rx buffers */ - ec_bufT (*rxbuf)[EC_MAXBUF]; - /** rx buffer status fields */ - int (*rxbufstat)[EC_MAXBUF]; - /** received MAC source address (middle word) */ - int (*rxsa)[EC_MAXBUF]; - /** number of received frames */ - uint64 rxcnt; -} ec_stackT; - -/** pointer structure to buffers for redundant port */ -typedef struct -{ - ec_stackT stack; - int sockhandle; - /** rx buffers */ - ec_bufT rxbuf[EC_MAXBUF]; - /** rx buffer status */ - int rxbufstat[EC_MAXBUF]; - /** rx MAC source address */ - int rxsa[EC_MAXBUF]; - /** temporary rx buffer */ - ec_bufT tempinbuf; -} ecx_redportt; - -/** pointer structure to buffers, vars and mutexes for port instantiation */ -typedef struct -{ - ec_stackT stack; - int sockhandle; - /** rx buffers */ - ec_bufT rxbuf[EC_MAXBUF]; - /** rx buffer status */ - int rxbufstat[EC_MAXBUF]; - /** rx MAC source address */ - int rxsa[EC_MAXBUF]; - /** temporary rx buffer */ - ec_bufT tempinbuf; - /** temporary rx buffer status */ - int tempinbufs; - /** transmit buffers */ - ec_bufT txbuf[EC_MAXBUF]; - /** transmit buffer lengths */ - int txbuflength[EC_MAXBUF]; - /** temporary tx buffer */ - ec_bufT txbuf2; - /** temporary tx buffer length */ - int txbuflength2; - /** last used frame index */ - uint8 lastidx; - /** current redundancy state */ - int redstate; - /** pointer to redundancy port and buffers */ - ecx_redportt *redport; - pthread_mutex_t getindex_mutex; - pthread_mutex_t tx_mutex; - pthread_mutex_t rx_mutex; -} ecx_portt; - -extern const uint16 priMAC[3]; -extern const uint16 secMAC[3]; - -void ec_setupheader(void *p); -int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary); -int ecx_closenic(ecx_portt *port); -void ecx_setbufstat(ecx_portt *port, uint8 idx, int bufstat); -uint8 ecx_getindex(ecx_portt *port); -int ecx_outframe(ecx_portt *port, uint8 idx, int sock); -int ecx_outframe_red(ecx_portt *port, uint8 idx); -int ecx_waitinframe(ecx_portt *port, uint8 idx, int timeout); -int ecx_srconfirm(ecx_portt *port, uint8 idx, int timeout); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/dependency/x86/third_party/soem/include/soem/osal.h b/dependency/x86/third_party/soem/include/soem/osal.h deleted file mode 100644 index f88bce85..00000000 --- a/dependency/x86/third_party/soem/include/soem/osal.h +++ /dev/null @@ -1,222 +0,0 @@ -/* - * This software is dual-licensed under GPLv3 and a commercial - * license. See the file LICENSE.md distributed with this software for - * full license information. - */ - -#ifndef _osal_ -#define _osal_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "osal_defs.h" -#include -#include - -/* General types */ -#ifndef TRUE -#define TRUE 1 -#endif -#ifndef FALSE -#define FALSE 0 -#endif -typedef uint8_t boolean; -typedef int8_t int8; -typedef int16_t int16; -typedef int32_t int32; -typedef uint8_t uint8; -typedef uint16_t uint16; -typedef uint32_t uint32; -typedef int64_t int64; -typedef uint64_t uint64; -typedef float float32; -typedef double float64; - -typedef struct osal_timer -{ - ec_timet stop_time; -} osal_timert; - -/** - * @brief Returns monotonic time from some unspecified moment in the - * past. - * - * This time must be strictly increasing. It is used for time - * intervals measurement. - * - * @param ts Pointer to an ec_timet structure where the time will be - * stored. - */ -void osal_get_monotonic_time(ec_timet *ts); - -/** - * @brief Returns the current time. - * - * This time is used to set the initial EtherCAT network DC time and - * for logging purposes. - * - * @return ec_timet containing the current time. - */ -ec_timet osal_current_time(void); - -/** - * @brief Calculates the difference between two timestamps. - * - * @param start Pointer to the start timestamp. - * @param end Pointer to the end timestamp. - * @param diff Pointer to an ec_timet structure where the difference - * will be stored. - */ -void osal_time_diff(ec_timet *start, ec_timet *end, ec_timet *diff); - -/** - * @brief Starts the timer with a specified timeout. - * - * @param self Pointer to the timer object. - * @param timeout_usec Timeout in microseconds. - */ -void osal_timer_start(osal_timert *self, uint32 timeout_usec); - -/** - * @brief Checks if the timer has expired. - * - * @param self Pointer to the timer object. - * @return True if the timer is expired, false otherwise. - */ -boolean osal_timer_is_expired(osal_timert *self); - -/** - * @brief Sleeps for a specified duration in microseconds. - * - * @param usec Duration in microseconds. - * @return 0 on success, -1 on failure. - */ -int osal_usleep(uint32 usec); - -/** - * @brief Sleeps until the specified monotonic time. - * - * @param ts Pointer to an ec_timet structure representing the - * absolute time to sleep until. - * @return 0 on success, -1 on failure. - */ -int osal_monotonic_sleep(ec_timet *ts); - -/** - * @brief Allocates memory of the specified size. - * - * @param size Size in bytes to allocate. - * @return Pointer to the allocated memory or NULL on failure. - */ -void *osal_malloc(size_t size); - -/** - * @brief Frees the allocated memory. - * - * @param ptr Pointer to the memory to free. - */ -void osal_free(void *ptr); - -/** - * @brief Creates a new thread. - * - * @param thandle Pointer to the thread handle which will store the - * thread ID. - * @param stacksize Size of the stack for the new thread. - * @param func Pointer to the function to execute in the new thread. - * @param param Pointer to parameters to pass to the thread function. - * @return 1 on success, 0 on failure. - */ -int osal_thread_create(void *thandle, int stacksize, void *func, void *param); - -/** - * @brief Creates a new real-time thread. - * - * @param thandle Pointer to the thread handle which will store the - * thread ID. - * @param stacksize Size of the stack for the new thread. - * @param func Pointer to the function to execute in the new thread. - * @param param Pointer to parameters to pass to the thread function. - * @return 1 on success, 0 on failure. - */ -int osal_thread_create_rt(void *thandle, int stacksize, void *func, void *param); - -/** - * @brief Creates a mutex. - * - * @return Pointer to the created mutex or NULL on failure. - */ -void *osal_mutex_create(void); - -/** - * @brief Destroys a mutex. - * - * @param mutex Pointer to the mutex to destroy. - */ -void osal_mutex_destroy(void *mutex); - -/** - * @brief Locks the mutex. - * - * @param mutex Pointer to the mutex to lock. - */ -void osal_mutex_lock(void *mutex); - -/** - * @brief Unlocks the mutex. - * - * @param mutex Pointer to the mutex to unlock. - */ -void osal_mutex_unlock(void *mutex); - -#ifndef osal_timespec_from_usec -#define osal_timespec_from_usec(usec, result) \ - do \ - { \ - (result)->tv_sec = usec / 1000000; \ - (result)->tv_nsec = (usec % 1000000) * 1000; \ - } while (0) -#endif - -#ifndef osal_timespeccmp -#define osal_timespeccmp(a, b, CMP) \ - (((a)->tv_sec == (b)->tv_sec) \ - ? ((a)->tv_nsec CMP(b)->tv_nsec) \ - : ((a)->tv_sec CMP(b)->tv_sec)) -#endif - -#ifndef osal_timespecadd -#define osal_timespecadd(a, b, result) \ - do \ - { \ - (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \ - (result)->tv_nsec = (a)->tv_nsec + (b)->tv_nsec; \ - if ((result)->tv_nsec >= 1000000000) \ - { \ - ++(result)->tv_sec; \ - (result)->tv_nsec -= 1000000000; \ - } \ - } while (0) -#endif - -#ifndef osal_timespecsub -#define osal_timespecsub(a, b, result) \ - do \ - { \ - (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ - (result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \ - if ((result)->tv_nsec < 0) \ - { \ - --(result)->tv_sec; \ - (result)->tv_nsec += 1000000000; \ - } \ - } while (0) -#endif - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/dependency/x86/third_party/soem/include/soem/osal_defs.h b/dependency/x86/third_party/soem/include/soem/osal_defs.h deleted file mode 100644 index 3d6ad511..00000000 --- a/dependency/x86/third_party/soem/include/soem/osal_defs.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * This software is dual-licensed under GPLv3 and a commercial - * license. See the file LICENSE.md distributed with this software for - * full license information. - */ - -#ifndef _osal_defs_ -#define _osal_defs_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -// define if debug printf is needed -#ifdef EC_DEBUG -#include -#define EC_PRINT printf -#else -#define EC_PRINT(...) \ - do \ - { \ - } while (0) -#endif - -#ifndef OSAL_PACKED -#define OSAL_PACKED_BEGIN -#define OSAL_PACKED __attribute__((__packed__)) -#define OSAL_PACKED_END -#endif - -#define ec_timet struct timespec - -#define OSAL_THREAD_HANDLE pthread_t * -#define OSAL_THREAD_FUNC void -#define OSAL_THREAD_FUNC_RT void - -#define osal_mutext pthread_mutex_t - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/dependency/x86/third_party/soem/include/soem/soem.h b/dependency/x86/third_party/soem/include/soem/soem.h deleted file mode 100644 index e4d43a8e..00000000 --- a/dependency/x86/third_party/soem/include/soem/soem.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This software is dual-licensed under GPLv3 and a commercial - * license. See the file LICENSE.md distributed with this software for - * full license information. - */ - -/** \file - * \brief - * SOEM main include file - */ - -#ifndef _SOEM_H -#define _SOEM_H - -#include "soem/ec_options.h" -#include "soem/ec_type.h" -#include "nicdrv.h" -#include "soem/ec_base.h" -#include "soem/ec_main.h" -#include "soem/ec_dc.h" -#include "soem/ec_coe.h" -#include "soem/ec_foe.h" -#include "soem/ec_soe.h" -#include "soem/ec_eoe.h" -#include "soem/ec_config.h" -#include "soem/ec_print.h" - -#endif /* _SOEM_H */ diff --git a/dependency/x86/third_party/soem/lib/libsoem.a b/dependency/x86/third_party/soem/lib/libsoem.a deleted file mode 100644 index 511036788210ffebe1d4cbac55e6e92e078f23dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 691060 zcmd>{34B!5760FxC7G;~gb>1_1Q;c#ECFQEs7x{m6CEHxz{IK{WFb)?m_^Ydiy^`o zqiAi_iq)#E+S(RdC4jb~2#Bb4qlzo;L`2cnec}H*_ntd*=O(n;<@5RfKmYgnWZt{q zd+xdCo_p?n_q{iHGbF#Nsk;8u#L+pi+ZZh=kM+--oUxN9u*keb%d&2=tfc4vJAaQ) zvaBEY=jc1gI?|uB$z!QMSKpA;>)-P?ST6o^UohVK|K%_KV5#+e|2z*pYUzIo^EX-l z_TL|UR)YSwcaoLx{r_sKmsU2{wzMv(scNZO&{Va^B2raT6O%4DbC%!*vjSr8FI5M# zrkyEBu{xlvizHG)#S!E$WkqzLq!<-PP-vFa8RAMpECC)U6sl(jX4Ez{H8!b4Q*Bj^ zI=-l>p`}(NW-ToZ%n(Oqkt31>t!i#=Sg=^+#gIb3&K6ZIF_K#TjKIQ%=9WnQj6h9o zb9Gb0l9qU%|7Kx%6 zMK!f`4U22*8ynA81Tj(~YeMz{J<(jdSo$6rO|{h*suI-Nx~8fHi)t53yNi411tBR> z0>`T7Dp7e2Vh}#6Z6Y63)-~1E>R@T*lGf(>Xb3E6ToO$*G)Iq@R8^l3hel$hdSPR; z>YAp;CF+>GjN>JZjSEFy#OtD?nrdrW7uQsAgIM;0*4kF769qAHV$wB@ zi)&**?b0O;O|ht{v8Ad-L!!f=tNP;Vg$=S#kRmIinrKW_E^1xa(jd$vH#UK{FeYZD!1@XF?zMls^XMw}2qJY`n;Lo%YO8*6GcF&!(> z=Em9{+88Mj3O^d+nkI!Jb2J1Z*%7;zR@Rb0Bm<|u*^Na)muMgoF$bH1K#UCgBZqRM)-zZsc0T-?%F&xf(oxybD~p^KRr&Vzg9f~1}Xs~q2A zfg1!!0t&7{dePd7J-vt`pFtGTv% zn!nIm($qjZSTnz>xpq_|FY1r9mY*fq9|1Cux%{qz`8+{?=dS4?i(ycE>{Bm|*ztYv-nQwkM}q*4pXm z0dLo*b>ZUFx%xn(>e>&s3<-8FAF%SP#EX(P{lsNiLGSHPH{}OAT6~j2 z;ZOEcJ>IT^Mc$2X7wia(8(`&aZQDMG4hLF=;d_3y^ij+D(K-IJ{pa}4-B#xv9Iz}D z%lk|eJh0t6c#5>|%z$^}m;SjW;qUx&{AF`y!@OYI2ie6P_dG-@U~zffs_9wQ$|7&q zJDo+zb#`&;>79XNzgt`CSQ~W0;-IS|c5ap!L~=U z{hdYMsOI@Aw~6*aSKeoawgVgLMtKLX7QG_+<4kUAQTR#fl;`J!Kb;-k?d^J*S{(`p z*pyHQj?m;MLgCNhp)b4}w{G!3OR!<!QYa2XlXvgb!2D2>YgmADt6^ayFS3Zt;1-Wxiyg z$zQf59>#>ihhf_x*cNPfk<5ZYf$+zrFf8O7gwKF48}<#3weK?&6I#xy@Q1gzZ(W|| z?b=h)*)q5^6n>8lCz-rwmDSmS@P5teSbu&`duibOzhrO9|6}&1{@-CQg{nWi%iHy= zx9hW-V2A6NTo@Llah10<)EV%_c~SUFoN9J@H}1z&?ti#DZA-Ff3eV{QUuL*F6#l3bbNNx) zT>eS;Lr~#Gn#y-zD$fX3Z&zKqa+$k0xn-yjikZs~#mwbXaMpk+H~}0LIRPAaJi0#c z|2y{6ebC4`=|8YP@jth}B>Y!-KHW~|`+v*{hQFO1eomh7<+8(nW>NSJbikv0GM!Cb zz+S}ZG#LJ9OA@uG^FFuceVpmB*pTj*hcRb*oHO+^z2;Ax-2G+K5C+l`#6m7k?#aP$ zvM)gMWX!CYowrqV@Zs@385H4L3w@KVq|AS?HFVZDRpU~-+mBQ&_Q#Qx}vj=-q_fA}e~I;|w^@*P+wM86(pS#Hbr zz(DH+`Z$U{^sSD1k2kLr^g3w$WO-e3=aD-*j-WVp9@}pe2{>p{VU#=QL>!NFxf^ir zMM@o)l^@AIq5su5_Ik7uBL@}&jq>E&Mmd^#*E9Vn$15uyfz_vExn#}&k>inxK_VwvCUQhh znoJB$I0t1iWn!2}WXXgt^AK{ft?nLrwsz4Z3)?ZW=4+tXVyfH2V`CpIUbn#GaP%<=rk{jCH7B$ z0$PQN#G|z1qcdKN@WB~xM~-vSw?>YKWE{e=*Co_>JyESmr+RB~p6u_%M+^RjQah|+ z$w{-GM@sl5X-?8W9CPB#L=tt#oC=X}`uu@X>qjKy!r=_-96F4XB9)@B?3JV{kw}z{ zNSg1Y8jqnaygtEcSt7*cr;K8UAWre=l7c%H|4Tt3?iQbM;xk@+dcY zvmCOxQ|vr)mn?TFa+fN1X>wQKk_Fsi=ZM+4WXdCVR-6z?le-+b8zXm?N7g5IX>yk> zcW${$k-Jp6^U9q?-a%)}ok#AHF~Y=x6LG@YiO zJb2Jbu8JFjoJ{dSG?N@_r8@zK(@~w9Kv^#`B71&36>3n~RvMN0&K8B}khZz7k2ZV# zomf4HY4baK0M)2AX2XX=m)e+35Yr|@W`Wz35cbSw_9AyK)u*I*%RqpV{&j%V8Wwmr*nDGtjMuDNH$b&kZthX>epMLZ>3Q;vx$e&}e+D(ywVsSySC z7!*vn<}TI}EqWqsx-h0FRXEdijdh(XDwPST8+%IGW30idaUYOR(#IhHL$0o*fejDHI9&Z4n7d zE~a;yU|es#^FV~7chZ(yp2GtYKXe~`Av*Ym-8S{r$>eJ4w(_`T7I&0unWgs^YZA_d?F3;6)0RDccB{MJV{4Y(d4{LS0zf!>~(UcX}(P6M+W)wSVu>L zWnMZvdU-Fwvb>jIS>8*qEbk>)mfO7t3sv~<1xtNw8=}FoD}&om-%GI6_Yy4iy#z~r zFTql8_Y9VLBUorfcV28@hyXf2Hh>~QLBVno4DKyh+9Ui(!Gclr{etCgUDXc^mN~XZ zA2)L&qw@Mb?C3f6Q9VZG9Q!!Elo*wB?4IlEIreG#Xgm5M>r7iLk{#F7MZ82ch4J@2 zBc)DGE3=IC9tMnM<;RB2U6Iz*_vO~)_YyYwy@XAEFJY74OW5T15;plp*w9k%)YuRa zbIfV6q11D&Qfxnx`JbNiRz%h+JmqMU7RBdFsDa{hg~n+z^TA`1BR&(P7G9`~k1Q|~ zuGtj57|_;2k-nw5EU0VxCksuQ7uEH~7(Qv_9w|I0WO0Y)_7a}Cy@Y3OFX5TnBRr4m z8J>Ubk8fO{w*NwSdXE&I@!8xOZ!h8L?Ik?Dy@aQ?m+>@XYvE;o0A= z>i546&x=t*Z{fKu!h3|LxYQKaKi?}n-_tdH|L{amTh`xWZcap}8;TiRUO^ycMUzUa zvcJbHXn2`oc7*rn$Gjibj~D5heqcX#9ibm9@r+Ik zn68)`Jz_j|9iuO0#CYt=(~&HAN>6l#esv+G=IF~EG59vebeb5VTVlGl=Xl&6zZs9W z{wEU&FM9UlEiwHVS@Y1UY77h|H=37-KaOy?x7SO=N#y14bBTD1uIl@* z`o(#xK4#JXu5>T_n}~&<@NfMvea$b?Xg%Chs~ZYu!o(e`$pQ=s7Q^Co4@Q8RSD2_oCJO#7#;1Lsc!MQpT zAyaUk9!>>IWdM0ZXasr&P*F?(^^BI8|E*|wdgy;MT5vB|Z2wlY(Cl=iSfQ_@()RDi ziicuFR$)ybRrXOSb`t^ucPl6{f_h{kO(XVE1@tWf=F_R)_;^0aTj|U2nL~$@j-#@Z zsU$_XkaA46DXos_1gwrX;MfvH29O9n7L!>6>^mZ21MG`5E{Ysa)_@J*e(K8}7}1sGP&T+5j{2xkD78wV#1u}^kSPAXBk?x20Cy`qnedmM2ol%I5R z5@k=qv)TA84dCIqPz0VJ%V78@jiLzGmn75-NoGB$I2mFdv*MKTy}(o@sVnsQtfZt< zlbpCmoi@rot$(Bu!~2)?Nw+iNr(|E!pBj+vwUUN-N^q1R4CAI)Rv%TDt~S$ck!atj zO5~a64{4PF*s;Z2O?rml4L)U zO+!W5AqP=|r8X7J8R8r-${s(;I<0?x|HY}~!<3Ur z{DgG-nAAQ=&cOo*lZVm}&m%b;@Pv32IvmX!-A^@3bix=kAt?#%fe+v&GCz-N6%jEU zbr2lOK@xH0IMI8eP-J|Jk74lli6TTfv>X5vM#16I-NXCmi0UTlGME{TrQsTF98y={ z&X$|MC4{8dTVq-7`@_(3v`&C8!e*1A zX&;fTJR{1^M5dtZ8C7T&ct@sP>FSU=XDM&UD$9|*CKC->BeU6AuDkj?+6S$Dv`;wG zK37@sX`e3%e%Yrz6IQgNgk8#csBUZeyXk1cyXmiIK;P>bZ)eEPe>>wF07I?}a&Kk8 zthb~bI$p?C3OT83CS<-LC6OknT4o{V)ePA1sw_Q=OUoWqQst1^kYS(e+5iinCK3p) z_tHBu(2$OdmowmPQE@anA>Ku~T^Vms;WtnPQbOol(TC9gcE-Vs#K_a>TU-Y-zRbYd z?Mqao;i&W{86JMDeT(Z8k}VUm5eP|HgK7clSef?gH2Cw^X}6@qpSPsnkuLptNBV;R zQGW`#GRWPY4u9S*<={^t$Nm&DGa>UJDbf8Ya%Lgt=5+Y;W?35k6s46l(w1^a9ZG{s z4oNjpf8Lq4I}JYCopw_?{3(iR{ydcSIpuzy_RDk>{w1nFN(e>#c}M#1(ly7qewV&E z9sb;mrfOLE^N#dCrbk@o`XkAf3E2oFe`+moJ?;I67ry+5_bab(qKq;@%M?|^Nzj>! zs)^=l?;)=}%XJ8f97~n+q4$8-E_WRu^%bf})Gr%SUrt4}FQ-1_Mav)ZKBCtyGRu%D zsH$DinTo2~1)ZfRk71Xf<$^kKZNgSbTmk%NF9PS!vet^oxc{y94_e3SymeU$P^idMVGEJLQCs&+wVDynK1be5vZae|f$ zsyj~L3gCk&aNI$upB?AAGUc}^aL#X2K2BlhV2r+;@;brSQx2rSmj_Tn`Eq6Q#$>c$ zWAfjUHPZzxQ&gEQ=uAb~beaK@f0qo`{f<;ssO;zj{&eD(iKybs#Lts3@IO!bB1x-B zWMY;{6jaqDDCQ|im6HUWrKnLsIrzs(Xx+!Mf@mj+Vim~Vo&W{BajCFE|j$SgnlUD*W{;PC+r*&Qdh$DZ$IZ@Abe__sR;Qo)Xy=$Zq$*740%R>Zx|m4IcR82G5@; z`%lPLp4t+>CmuO_;_vrFJr(v`>4|viN-8@OWp9iy<*Bge8c)P2u4_oJObA9Gc`A&O zQBUo2hvVR>aNL@Bcxp}j+IY=VBC`ycf+|l5I#W^QDM4o`8ugUm<>0I0;i*-!f~coN zb_KHc#K9GNWKB^|?TOnT2Y>93Yl}yA8%irrT@m*y%K24XdwkSWd*fb-i+Ji4DmxQp zyCY0_YH!>daS^Au-XOs;AsB(=sl6x}_0$H}UKc#I*Y$!Mo_fLkqFeKn$SgyqpvqH% z&Qw%+O3+z~Mm;5XIruYfcEtBlG*Gj*GsNXUGT=Iu03veLvE1S zWjyT12FA0_j!3ZQz-Hnn_Ppxx4 z;Do0haNgp=dG!|8Z7$7IBC`ycf+|l5I#W^QDM4o`%ARrwS}v%5UKO|k_+}Scd$X)n z`R76B%TBoBW#=zl(erAD>uPe^)vlXdaQICq!NEoTbKPoR?LaH8cIde?#2A+^pW2wc&HeEofyW1|C4F9gG0j1Lz< zT#E>~xjbZKlaY0&BkltF0^u#9zmr7MuCoQ$fO7P0OOc?CSCZ8GF_Pp?CCL>)welPO z^#*?1$bO?#wEak_0zagNNCV+DZWkX(w~|_i%u@4*N=F4SCEP+ycJ8T-H2{GAWO-a|HyEX1U1B4gWx{oaHcTgj~6j(?<>D zDp(FfM#xntISd)`Jy?p@tO_wu&@g8+1`IVV4k11a$J982mXo~z;UoKirhC`PRgP;s zq9FDnD1Z>R!cRuro_w~7#k1#mn$tL7=T)g7M4_M9N`yQY6(YYVF%CU4(RFgb*@96J zn1}wHSLJNRv1s`m(Q-gmjBaMN2ZiT4I)Qd=^>_{Nuxdah(V@&0)vZ{y(VzrF? zCH+(uHCp_uFey;~wJ|ofyE?qveYYE@+|6!UitLTUI;A^~)^A}C7I!y#Xwkbl0W0EN33Ms4D+$*r zdy_2JUFmnF<9@}1>6UAM#{LZ43i>9)a^0SFXBKWEK9FU-i^r^F7Ckz`f17-rnnF7Y z6#U^}u=W@h(AXUIV22ma7=$!`2&ae-5tGrRaU{Z9%(e2|;I^#oyI)+UduYA8e=*pFneJ4fyaJdiImO+V_gd>jgWUsQp)1bQAMLw- zg=k=6oHNyS$2*(y(a!fQ(a@V#l*j!7<~Y4|W@CTBtvz7b?zvDl@dEeU)7*a9;v!LP zJu=9Q%c-sIi6w6PjGXdOqo~cDE9!b{^cLCZH!V+wD0xPoz9^gGc6o#c^5OS$pztjB zptw)mu6*~Pa<{9+J?Iy1*JXfkn;UE9#FY>^^(?CNo>8Ml#W_>n)|GiKb0@ALC2^jN zU$_TBk18Agf;({}a!#Snj+-W0ya(T&clL!@==Qs8+nt#YoalUUdHH3sw|{Hfk84$} zjQmLxTp|C=$szR6)+ZhI0u_xDUhPTZMp*J$w>S82pT9KPuXj5l{rbYCk$!dP{ra!WsyL!qh~4wu_C!SP*%S%AMXn49Tm>esg2*Xa zn|6M)FG4*&bTTQ2y#J%k~-94y|h7ZT|E^YX@ zkF^{w`*ixqcW2P91%)nukcP@SjLK0MP_sqYzrHNm6TfhxC(6YLdvlrS2~2^1aMBzY z?dSit0oyeLHbo71EMmZ}SOehci_xR(26X6S+EjE;)E}`ODjJQ6v>d0Ub!d&}fj*eR z<(x%xQ4Z!J1pZ}lo-ytOv^VJtcfw)vvxuxjPn$cbRwOaYVa}416nj}Z7_(s9kHuW% zxs3elTq-?z^|E|SRhiC@ou|X;>x9#<6m6#3>u4Uw_CoBc7SfxOTk#H)3F8H)!$~;ON>)rS^cR*zNKsK~VewRyt**D4 zs4nq3M)5XH^_pqgLtuVu172ynxN=eLBD~sBh>15#FKk@AAV++(E~Ym(*Vg3FOH_&q zD+{LuD#dH1RrRHTlESi(#R|n6R?+CDY9&nXvR1NzkUv!7pKUcX;dRWy3uB{sg=Hnv zipCh}JR@z&k3%DrYxv#T>IIb%W8wE`d#D-QqU!ppCQbe9irImZ;$q?m%G+)haztul+`7@LNUcWvW^vz@&h`j0((%m`Sg~ zjXIT5d7`FqQBM*iKPs*ARiw=k6$s54a0>@4d0D<0T*XqU)lBawZCct~rIjhHC=A7P zrvUVt+)BJ!xaHy{wW?9{zHt>HrA37^0#PRiLKqcgGKj@nM`OaV1usEvSWNvbgUMQm zcNEjNDDkbRMR*~y?30)xm6hkyTZ1brMZA(Njf*($#rx2?lV%mp4Tz4@-^{LTlrdZ> z-epTKA8)QlsN}>{qeP2l7tWp|UuYZ~PyP@M8e-%SG$A^AXHUzc!7QRV@|ILe@vswt zK$D581O*CbOfQ>-R%zpzB1}b1DZ)sT7Cz;?0%WOIymBB`RyJO=7_Wk5Ulh)mRvIcQ zi+0NFY0-c}-J0fcR-{X;snh4=6y{CL$;%rxe$<$pk&9Yd7uTM$pca}Ns*$AMZa8J> zqzNM@jL#XlAZO%^u{k5_N=pjySw*i+uW!QpeOsGHwl2O1JvOqX3S*)fnFxdG^GD*H z%Jkm$#-@?|uJDe{>{5O>NU+ zyzf}NR=Kh=au%pt*oar~E^2J5wJ=rD$cT<6Eiy22D=>D8XBUZCfnJlW!oV*_RwQc0 zWJvn+CO}5S7)?G(9NxfWC+p{cr@1cFxWHf21dGvd(f8_PqtE)Y%8O=2M|iPJQgh`G z72q`0*a}@xLeq%ygE*tmFC_3tMf}s`C#G4RA$dj;rsM7B=J^!!c~Qt;p_+r*(`BT@#VM}$`FY#R-|fS4IDDTq2rdow0EnvF%Lkk5DmM}2&rqBBb5{7vDk zgvn!kJbi|ohxFMNzwZG11BH0&KY4;y6_u5XTk%5w=6;qWQn9qO{1OmvrN8(w6XmIJwBT*K1{G zmlhQX<44ywE~*_}z39Rw`7yh;rL?AT^z0c$(?-*5SY!P&39M5_)BH=XoUZ<-lHW_0 zHM+IAX>`Nl>V>T}wWBe)RZbW`a$&>b)}Mc5TH)G%2QiI>E zARu0GKC&6hsaj0bk?f{c{IG^tOhAakJ(^pCfE$Uw#ZnceCt-}shkT{O;F7leD z3vrMyPH-r8s%1?rEIcVEcg}pw9j$zp8JX8Al6lL=jvX~-{HSpwID_tSi5t9W*walg zoSksz+}cWa#nioAeBV)Q_9e3{duf_IFgf0{4!7m*5$u&j5v{MC-))ahpmR*-M2iZ93oQ*#`_#8i(zN%*>k^Zr|Yq53u* zp+1Tg`WF-0{u{AE9l$`%V{Mmk(j;H5|2k>7$uw* zUc6&$q&Y*gU(D@b_{U56x+g>4cLaVsIO(~S<;5J2K&ucDPXP4_Zgg?Ig$K2j&*e&S;Kq^$Cl#jm>*IxhcsM@9}60bNyBVUF4`Ad5c_* z5RCpVib%!&+{*Xf2Db@@#ir%|l*e5i5i8pXfSIK`>r$vhsYUc6Z; z!slvS*MF79b^Yr#uIs;F#ftcUhgk7uGf2y#`Su)Yh17QS&i%U4&(WV zvhhDvk$x}GIQ?(0ijxa9uG{&0jq7$ksBzuSJsQ{Td{g7Po#S{OBs*zcr|g-faox_f z8rSXoi^g?3pU}8&=PMf5?HR)JDcM8+6Rhkh*SKEqRT|gpU9WMy-g`8z*ZZ-?2Wj;V zq(ueUU=*E@jE6J(EG?{JOl^-kBgUN610l{V6& z*NeABi_L8BiyGJKeOKdpy`O1ZuQy1Av6=eUXk6F7MdS4U%_<)DXk4%ND~;>*PNYU+ zGut~~<9eL4`-P9GjM~)ySW`7ZhR0O~3lOKK59M^0!8s{kn1BSGHh8KQS;w&e>EVzR zBv0d1@2`3MOfvZUJfEIs@VU&34ZfQBG=ty7=d)P`KaQ+&`*hGVb^54ZfZIu*Kkw%y$}m5c_wJ!PUK(0|x&@ z^_Ri7vwuD}csi%QHMm-*%Abgk=U7tx2>T(E+o${!=6VMhd==ZDWANuWevUQxYSw>( z!An{HScBij?V4opE7+fEJ+JIMll2D-dGR$+*mSzVZ)N#XgI~b$qs}Kv|1g$2+mL^N z<>wn*#bv$0FXi?&8a$iRR~UR0k9T!{LD};q`(Mp-ihs-H*Bg3vu${LV{8>)lW$-a< z=N}CId-lJIBUNuQ+w*5b{uE9>YVa)f|89d{&vq)ilzw$zVXq;-kK;|v-%4ICUM%Z9 zLtg&ok!7jtG9{nJ?fTS^-^X#NuGf_OD=hEib|^kdl{ffMZdba&*R!9q4c^S{8f@_A zSpHap)31HdHqzkqvp=+rH~9HHFPv=f70e3^-pRbg;Lmcsr3Sx^<<+=U{!!PH=Nj^_ zak|#v@@JPUYoWp4=XA5dd|~i-9!K(*#G>b^4_Ll0$DPtoFKeQ0sKN2#MzM`H_$S=1 ze1orH`4WSx`$TgM{xREAZE#j&{l?&@^7#FW!3)`+yA58#{6&K+JKr(5vhx#zD?1Mx z{4@4@3j0I(b2G=u5Q8f_M;Tn%d9uNkok4>uJLec&**V|f%FafES98CsaiHv1@enrT zRXqI6;6u2*w;TKr`)h;2Te#jo8@!&=j~l#}`~3xjtMUJ~!Mj=hV}q-8v;5J%=(tGc ze(W1BBTxB(eq@fe2?n3S@^cLSGfu1POQq*Uj`LQ|C$)ss{rf8oJwDcdy~fFvnfRma zCWF(xY1(epIJw{*ZvSsJPI^uyVQhae_;`t}`!r5^syY3T#!1f-mfvRZh0Gt*IO+L_ z`Ewd4J(n|o!QhuMSL+4k&);zWzGuka#PSCWuI>vQ(m1JD$Nu?R<7ChMtmm-7f5+U# z^(%W$=6NR5;FoZ}tLs#fC;fX_k57{)zK8jEgCAl($>8rZKSSemSi<$rW3J+D4ByXc z(B#PlSF)a^8Yg?yeTQWRkK=LCZt$a+tLq-s-u}$5HRLBU|CzzZF~5m9C8~_aKfroUHTY}H3k?1e^H~P}JM*~) zKgj$?%qdCnc^CV!+2F4*f6(CXGv8sx zQsd+|b$=tz;7b1ljgx8>SLz!Xq@VOm=6Qy`vqGEVdCX7OIO$PwHAmy5=Ny)=FgX1v zBW>quob;%;YS1|8xrpT#8N88slg3GpimOXCPI_);`Bes2_pPom_=7C}Q-g10{wssO z#Qbi9Kg;}&%vJx^b38nt$ke=Hh2Z|4>e9rQsd=Ijgy`*%YSR|Rm>gy07==e#!HIENzdEu`C|Vr{nFrfGr!m1+nC>P@Q0XhH~3!Wy9}=GzdWUJa)CNu?A18g`4Q`R)!+x1 zzo~K3qs|uxHBNfm^nvYTgVPTc)Akv2)i0m&b;WqE%vb(z;r=>}xso5l@^cM&<^L*; zlPNy7r`F&{GjBDxpZUcGKZSYN;Ab-LH27@h*J_-aq~@`kHBO?7SpJs=Z(#mwjqCk* zuf|EVjpgq*_zLD*G_LpKc8!zh&slz#!LMV!*Wh>;huB^<_@9^`H28DOKQ{PZnSaJy z_50b}@2T`925gF7%e=zi>iYjugKuE@o0+S4yMgPy%aB)JE_le0SMm0^#!1Dytmi3% ztM3)PX7F!V{!N2_!Tb}0XYh6UX9iDY{+xz_kCOo! zClgO)`J)X!iTN<*s=tn5|5s`9q<=ck4+{->_5IT}Ltgp+Dvgsp>bdQ;20xSSxy|60 zF#ol|FJ!*a;Oaif7K2~O@(*d8+NH+(;~FPB)%Q}KGWcz*=Q)k*{rH;3Nza2U|E9q= zF@I0vdOv=mankb~%YSC@Cz;3b7k6m9ko+O$i3b0G`2d5vx!;dA_+geG#$3gT^5@A0 ze}v^PH~7=ce`D}{%pYg2#=Cl#&mKcweGmErLte$(=NczJC_P^rd?2?siN4f`P5JFa z=3axV@39Ov_!%re%-~a*=W1N<-*FlzJ5?M`H268JhknX{Hs$|`?6)fnK9~6onjWh6 zJt~Opc0*o$-+YT9ul%`P<7DS$Y|k!(FJr#f;I}Y;)!^#B@Iiy$&+;D|{9fjtX`I@n z{ATkFXVs6pS>A2%oy-$8PI^AVA8ma#PVIe*<@+1_Rpv))T<_lz8Yevt%EUI(;NP&G zQw%3Pi8*H;1ijj%Us3JE;0(+0!^Mm=X^nN{h@IZRo~yZ#Nem1{x*ZF z`?G5d-o*0j4E_t|*BM+rH@}g&vVSkzzs=y6Fn`(Le`J1)_|2f`INHj*#NcY3(7;^H zOJjI^wHWg1|2#Sjc{PrHrg5rLJs0@7!GFzu_>IBUeY4*i{6Uugi^2cK{1Jmc%Y3KC z390LaXEjcCe#r8FGx&by`*f7UqZQT_Xv#z{{n>v_W9YnZ=c@Ozl=Gx)EWA2Rq3=KnDG zBh0^LuHr;}?;(!9#EgyNgofNa{L$7|<7EHKtf#-h_b?x7a3?9mcC5j_W%zL2iIN7Pj(L#-rohw;> ziNTjKZ`C;IQR8TZ#!1f&EZ=7EYniXrIO$R2=vs}Fo-UUEnZfU6{wst3jrrXMf13Fw zgCAo4pus<2zMZ-1_XLiUr!;vQM|1E;+p8KU``vsW{0)O2X8rFQTs^1x(BQ*a&*ui8 z&HQVF&tz`l7j$V;_V;J|k2CldzVBCH@K2auX>g16Y%sX`Uft8oshQNjlXx7xWXSL1 z`|}?g@@gD?t#R^a6}RiK!GFX&Rs2w3^txj?^9+Mu!hDFquVa3U!PhZAPUHGG8n1D( z^EWI%$>6_ceu~ERaWqxqq~{TqKf~Y;GB4G*K90`PIO%zV<9G%Qn9IEFgKhxxC9G#9o+J2>RvVSz|x!d3)nE%n>XENVt@R`gX zGWey;w;BA$%pYT}>~G+4@rl6`nJ4lWrIdUD^D=|0?@uo^_=PNg19LTw__n`wn<20M z-+Hqlug1|fjgvn+xLrF9-p>4Q2EU8>O9sD#dAGs;!u+7Yw=n-iNM2LCbh zH3q+vd56K(^R0EvmHnI9{w)Sq|7Y^7!LMccLG(fdY)a3q%%>Q92lIK%Rh)c9PQrGc zA+O$xu*#5EUzHiJLK^7k5iKlA$y{xj%WD~4So#sZw($~ZXb=p z*wj1_V4i01bC_ou{7mNknXC9w|38*z@J~np+cJZTFZ+PsYH;;`Tw9r|czcBB^T!SO zTS+;#Hw<|dZwE9^epAn7KQeeTx9cAoC;#(p1nWDElb+jI-Z2;nY|5WEF;6%6Uzztc z_%`M~gJ*lC-duxwn2*%Bh+7_y6E#kDHnRN524BGZbc6q#`E-M;=aKU@F6!lV=@Nr~ zB+a*$8~jb4Pu6Oj?0k~zz0%-+W`2#vsopo~1KUj+Cp|%aj&+N{r!c=m#lM5&{83Gwy2OV++Md!l>A!;YJZtdFnZIoCTbRFY@SB+LH~1Fj z9~k`i%s(}FUw(e`rNPznJVy?-B6_}9!tG7U5m>}OwRgNsS_3pr?JZ{gM;m+!^Wg?> zW`4ZEFJM02;LDj$GWas)euJ-NUS#mgnNMe~`d790%p>qchMpT)&jkj*p7{!c-_5+u z;CC`#%Us#Bg8hHD!3Q({;}Q5{hJN)t@(F`);(GTOTs@E6Yw*Wd{!N3w!2DfZ@eref|SM$I4HD?Hs9*Vd zspkLb8YlT7{M>Gq!TU3>GWhAtYYl!X^A>}zWWLnkOPQ}R_+89941O!~pBwyX=D#rb zW6W>YIQc={@4DCEYMy_{;A)=VrE&7x7p(tDgCAo4tj4KcHP64Ianf@PKX2P-@S~V_ zGq)j2r<{}VN82ZwJk3{p`C@&eanfJRden<1RQwb$PZ&ztqH$HhJjLL1m}eQhiTMD7 z*D)VvaP_=%xWOM_`7z8@d)GkK}Wd85I{FmE>a3Cx!>`R6Nx-_CZ@*r$!+#3_#C z2QLRB_W@KEhntY6M^GWoIF|RMhxlSnA7}8ZI6cbX_i}oy!C&I^1cNI-pJZ_LewKj2 z)%#XT3_gbf7~6EYi?pkf`D}x$|643K_$4fVj=|fQ&o}su%oiB^F6Ijj-o^X^gKuYk zp~0VL{$qo`%zUN6)q7al4X)n1cQtb=&O<_w9OlGyKF{E{a{3;FuVZ_@F}S+#TFUuK z|30qd4uh-bhF=(5J(mx0Ii*K^|DoRC>ib3N|4x*=`W~11e^bTh@c_!@!K1kPp63*U ztM3~tdzHNU9=Lknh2mn7OTOXfLW*zYd9U2y>i^*GGPwGGSZh@C#f$L!3fQ+a$$h;# zYN}mWH448@(qfGg@8KUczq#2OC1_3U{MH5d@s+wp2gSU~_DgoG`4hiF>_(;>wd(`3=14b>zk3fvVtGw|e5O_a7r}3us zQTTa?CACd07qh9g^_B9McQ~T{vt15~Q%I?KQfZsQ2kQS66<717O4GWEx?+j~ykA-? z4^;fn^#s+!-74M*ND?%!64&Qu;_7)99qSv1e*~R?BhvZrmZy2w)Je)!{fCE9D!Q4n zdC2Om{V$#`%Pg5H_tbt`Z<_5d05f$`iC>MTPdpDa8fMCtLdIlNZpJwJw5+RmJtI>+ zWvQEbEB`9nJw_Gey(v2nytnoT|0D~j^3;AhZ<_6I0W)<{$&?^{M&HXOQ$1zF$mp&7 z9b8_$5AI0iS0kf0`b@-5pNv1#{-1(T{i>Zf6x&tQG1wMRqqR-hs`j@cueb7_ zR>(qMDnZtvl4`%<2<1Qgku0FrAzVD-D^-3omsj~}Kj>_A6uVdD6y1dbTI$lgt#3J8 zK1YW*R{m4?4IGe8v-}iQKMfjfGt0k%yx#h6?ftUQ1~!=buMhrIdH&i%uXR(&DOL1| ze&z9(g=1R#ndPZ_jUftx_Z!rRou&TYU8Z`*D&2nRt^A}eS>k{y$a`*1gyMH4svB!Z zHIB}a+Zf){P9Dq83FF6$oJr#+jGrWq<|VEu3Kcu-s74!acZjAURakcgqI8PDQ!sz` zv;OWq`YZpLb>5G*`sez?pZVwb!+ZQ?{_qoXg5hsx2g2KeZ68dk33j-S$wjGPIN;08 z+v*+R@|A?&3+@c~a!8kVM8KD}pN>M|?}Fiv0&8o0Sv%Tu@DG@FFzoUL!(~2CT_D`z z%fRPCUnUBQ&I_OI8zjmFJD|8Gc)YBp)f;SkbkdK`@t^HK$A7NBa+{Fx4h~psFKnIT zFAI3P-klQ&ze|?F%<^EzYTr5-f!fkSoeOTq;wDExk)<38UFlo9gftPS}*dAkB= z$fOX`xzwaFF%1mnZKak?K>Sr}zpSsmcyE zY<(K}I|^NvcjJTZK~J!f&7?2<$ir{C+8%MPT;`i(wG8TL@#So$ewniJJ1hLTcj}j$ zY08C5BZ$OulXh{l%pHg|$f5AxqdE4tS9?*y7f$$evH+lzKP~i7&^tA7osN-zkn6|C9cVm9Ke{FuB$%mTW7Yf7K-EO>1 zJmRmS4u5jQO4ueG+x_BqhY$NZDw0Y6mCz3>?UK$5(%SYXdb=K*yq5ye+j$fwOmEkV z!H#9VG3Y+oCo~Yy2RT6}EC?eE5K}S?FxW!j{Uza71L3brDEzn=DJ1Cb|VfSh=gIVqs*5reY&=d=)?P5B52ySI(HU`pjlhs-el!acooD^cui{C zI&W=(hobLPFd1@d3zCGr;cck?s$Zb5dd8QCps!?Qu_H?KBo$r0fC7P{!S88%)4TF3 zr*}1-jzvg_h{#{rvP1;~g<{0VE#DqK?Cp9c6#fX_g#XYkVG*1+Ms$^Es<&&G?J2SM ztZea3O6z_M{Ss9zE%R@pxjUlvD5#a48dF;wFnVz&V(opz8tiN@M;zR>&)Z&#J+cze z!3k~M_?2Pr>Ka1f*F)h0b!}h9w5%GSeq!NGNt;l*+0SEn z+lSD=*ePBn;@?s1g51W3B5_K+bsbJY36a}(P3CAU2`47r|Q`^zo;C@|A-(%P6Aw>HxbVpInwzu0t0M13gy zi8jHAnbLdJP8ilRnmeu(=a9&p7#q=Clm@#f>fKoE7So%zeI}T@QICoqL7C-ok26(dekU)G2Zjg_&~)gcws#+< znPl)33#ZWWs2k_C*QsBu?(;;373e5V#%zV)ip(raFe$8y4TK4D>QI3&TqIQ>^hb)$ z25tKj&_ClW^mmST^?1iQ)jv!*~_3 z5UO70%b~{EMUy}Bc9u}5qk-cHSvX~8Bcz-~Bfdq`$tQ!e5CnLtSZpt5}viQq*{_x)JW}*^fLNX_BAR+bKp2 z$mR1We~Ncwf!n+BbY5?a3wSp!^trI$NcOG#66=i>TJUrS!jDUJk$&5R+1A@t5E;L% z4}~9V+qW-2r*&i9I-FP1($GEGn9$b2x7}BeG}bw7-^Q&tV-sCgZKi9EY1QANpR%pM zi2d+EYEj<7&VXgZ4W8~_qFA7#0LzG8DnA+OL}!W8(9*pC;^urm4XdiTXb{%#0W9Ln z#Ns`?J8u`}{9K$FlY^aDT*b`nSmw|SmIm8+l}pp4KfFD5iO7qLlJKJMW!=+2zaIie13xz$E|Y$RuCVNef*% zzkTOl+fEp5Sm;Z}ngr8)oqyFkhvA}t&yBdP4R|w{3BXk2`$uX|P6%n~IC&raI zej0K5YCGaexELdmOo1!wR=suD7G{-HKjGg|gfX^in>CL;>)%-ie~I{x zJwOX||JuuWDu*wG zIW(Ulii*@obG2SgNnDdG^I_@iz*^ZqdGNA)S{7lI^mIvQtDHOh;lT&4!IX#V!=jF& zh6x9h{%Xa%k< zV>%pHh!hl4==zCndf=k=Y~QKV!e3&jdFto=KG6zx27JZhdazVf8KLld!nDJg_yY#IJC)?&GMp|exjSrw-ohDhuEn`pOc8+{6$)Ksas z`A%4n>+OOW5&yL9qj_^(0K*`+yA8*p5o-L4bxle5aLg=Pl=t?WVD;NyZU_qQXD=k7W=ZhAKsS`w6}*y_d(J3C7t(;K6{?Sn$|gJ zbP&@b=D9sVgh%$Y@aqTqMJKqRaKNkH%YH&`@^)Q!4pmgw{%p%T!fJRwZ zhLlQE5h%Feq)X8-#+z^#<^mybRt1eg54v4kv$WhuvkMj~wzq35PBjx^BorzcC^X`aQkPtLj}XrIxKYIPq%tEZWZegv?Ej8C-b(G7pW~;L&W@(@A0lV zgt~*(+x&ll47wU~EM1F@6VnCXJ_?0txo|vww)Bgl@aL4{IWU9f$JH2UkD-_Yxi~Y9 z^RN64m!iecNIA4J7CqBh`t4@AtX;e2vE5iKe`R|w|09x!|4GYyW4)KVQ9ya2DEu9K z6@CpBj>AaWX=CVN;ug+8hyPW$#y>~&!&wz@$OCefxbi!Pw|yb{FyG-_P1kj>pglxw zgKIpb%_+X&zhmW2*x(csFYM}Gj3SZ#l=klsc5^A0uzbf#vfPECK|L!j@=x%0{v-|E zBrKsBXEX)S`IyU23RZ`FCj>jMOkdm|-{&ZefB1d6$2QK1JAhC2^RAZqaT>Sdr32Qe z^?Q&`aoxjcZV>%Lf$Tx6|AxP()~+7A9Y#>Q3Pn{|0h|=tg6|L75aCFyYH5T zSECKwb}GMPwS&r92X+a)6l$LCxJW(1J;Eh8I^GtYCcL$F4SB2kIY?4}tA1{Mwfk2< z>&U(|JFa^?5^`ij$2!T&@uAIR;(qS5@S}6WpJK5jrZKUqF~>%*`VraDJ4HJ}ovxK? zxSY{BNCXS5W93v4>dYJrKMXF#ToR&_o`1(>bSy_uDNTw2VO_yFh&QSUg#qAsvj-Cn_!0S-fm-)#RD)Jns{)2a%C8-*u6#$!ZL7DoPGFVRsyki{Lp6oQfydVQH<8!mii9c_Uh!}C0Ct}a zUB#V)(gJJO3u}s2zufY$zq9b$eEXuS41)t}%Y38B+8nYrJFxa_z9S?|&L)#{+P5MB zG4JYW(2P-lB*ZWl>cZE(x&}u1SAOkm8Q`~FzV727LVgirMdhpLNL}oNZh*ap44y?xn>|ModtZW6d8Pi}h(;_;d%YpWmlNW-eMDz2M08hquzA)Jr4W zY+EI`emACbQRoM9-B00IJse@(eep}3ndNVEk zVr~#cTgxi1gq9#y-nn$!lka-A?OP#ke6B=Ze!O?}Bx;fv9I&232L?xNquCiA%i{UU zbQA&si&Z|KdfVwcGZ;Jdf$&!CR1}>vM0269b>o3YY5u@m-oBJVqsuN@eb~F2eru;~ zzXw;-{)ayr?6-INpFtk(iFmtkAK@wci&HEtA-q>7Q$45~w~0#~;iv7dR(&NiwxLl0 z`wO{V2v2ojoq4!r6sZtsdS~(l!Or}th!wd$$4y^Bvb+-7%&{L`@6oT}O z1$Vd4_7xxzg!y!9B}Ca{V%*$)9n^XUuOxB*L)0Aqh@G2g-#t={o&fnKF2*^^+wCCMc;39FJC^>VlxSi-}q=3}|qJWkw98lYh`Kjv^?Vx*!0TXGWK^ zb;fut2|q7x1z=@VkEQ-`=J@n>?TC!YQ0MppxdaJs3*gehAHK@vp{WaJG8f$@pgnyP zA&650{hlY($Zg>L+O(g@+#E zw!%&a>bG_jxNw7EnYjFo-X9inFcS)+_lCtt$I#wc5NEA)T6mB_Q&2%7JXP$%L9(bt z-Wj?;MZWet4Cf>MR(B$5q!2 zqW;2hnm9(E1v@hz!s_S+>M2|}2RoN%gzPukzVdk6XhQnz5tp|ecV8@A7Np^%;SH=! z`0NqP1W!|KotZa@WqsyzCHC_LJ67k2{_dWLAyBstw>i}q6VGO7@L_7g;G;l~1hI&H z5yWEL(SzB5KKO)-yI;cC33W`*2!#`HUD59gTz#Z}5$Y`Ql(fC^MQFq>`|;q2zqY;k z1sQ%BJ!8Q!PEFy*+aC5z?-<%I?(BTb)6FQdl`zE|B{hRI+fkZ9d zu4Vncs{{M4dk8flIu}OQPjn|uobcq0G`bg5j*004x@kB>-U{;2sgItd#f&N38KAb& zGZhSv8ebfFfQCo(l$U+v8$%c?7#8&WUY+2?Jt4e@HgY>Co|Y3wychgA}*1D=BWKCi{0x^~MX=$lE?*WMsz-+ny)3Xq#jq+?2t^XU~CA zNn?T@nA5{N7w$yBLK?H;^rmjlh_W#i(y2thOHZ$!$7!tGykTVJ$F<&!aY4^(J$cW1 zH+~)JoSxOTKdsK+=CtXyMDP(YW@xO;cm(!&{cFok#;v63X(gSRKduX3LI3@RWCk7L zk_m^IbcpY)s8K_|dlmFP^mNGHD~1f+1QkQ(RUFWe!O}K8ur^%`nRmcQ7&9b>oNH&$ zNtZRN9pcBcx(6ZR#CeX~5r@Ik^E{OUukc7I!=)Ozje=+j^x~vd<{OWt-voe3IEB!O zE1!hr{gLiJi2*b>D-=%mho7C%ad!F_)5G`(byi>uy@fHf*M7WY#4a_4X2Tq944u(2 zDE*A^pgv*@(fBUvNT(<8=|vr(^lxzE6XPfcSOiP>*&=UODE&LcIX=5RqGf#jk%!gV zU*)|NaZ_5}O$&zi%+7lmV+o5z8`I1^W9fV%&)eabriH%ZSz8Yr8hK%v}0Cq|A(WUj^WM3=h(-};m#j?2oAWS`LgP8?^+-(Qm1BL@~n zvTq$g*^@k@>9SO2ZyzE`Xy&+{=|?4o;m=BQCwVdvrgU9nxzm!j&|W6`_I(n^9#ke8 zB*o*#r4=gixD$ubu@sM+AQB!cZ6KJaI&Pwqjzz)T_$<%ks$hI}JT+1ljPIXD&2x!L z;|GWwk4y{_Imt4SBXZ;y?&60gG@wkT%o!#USu%m;4XVkuCg*wz6TZOVK&r`8G^i|c zT8504xlC1l3@JOYl+ zI3sdAIOEL7aZdWU$nlVjY8+>|L|s{)XqCyQdOt-@5~-tKXdgt4Kj^l6)FDIdd>}d} z@TV<~ew_Wr_ z_LHd#l3%b>u0r|zQ>p**a(CDX$*4xww98KUfP`dfx1D?;B-3Q-Njr(G$dIYO%2cLI zJ!2=5O<6MaoSjBa$v%#nFo;|BrcEY{p}J0)l9H8hH4ab&Bc;DM=ET6%-y%bf+mxdc zw;~~jYRcf0?MP(HGDCz|j!X;_V!1MLj7Z4;dq^3c^jFYHRvJwcB0NdE7@J-dq-pf- z>?9JP%4D9EwgJa!-6$-^PD-xxD1f7Hs7*OO5A(6CYGmr;fSefRAr(_(&X{CU8W1yg z%2*F6^Hs{!c#*2%)P!WJ(!j1S96+PjX$QE!kB7` zxEg7~RH5U#TA3;lN^aoP8KT_MM`hkLQEn5bW{7fk$=^OpnJK#b9!|{`c^f!2Cxgu2 z#HqQN)Sp{9b*4z|=Tx~!rR}!T$gk4b70y#|JPhu%(ozQxqTk{Yo=6>%)I|p}F;s-3 zobFSH31i}9j!z`wW#SkiCIc$_kSc9UJ>FRe zbA)Z4)c-@$-|{Rm8RT^8Y^1dCt9aPoh8H_x=6n^MRS?JI{H}bDnn2 znL9UkP{-7eR%bA?iEcxfpwC+@D?F67c16H2=Pd}}frclx?vM5ymt%3k5&5iVbzXfi zJc)jxNwq(gO{%wrOgc>w)R`GWVJQ5^)0x*QpGsI9OGTA zWNOHrKNtbE3QE6e4yo1owLp08Odv{~d46@~`PG@H)ZtE#OQsgd97f^!tQwvPvpq_f z+A7onbuXaqEley&v#mlM&!adLuo6pw(m$f%`8AMH?wM5%6JI}jcLPgt$#wi5_{i+8t2KE>aY_5wV{1H%Fq({X`t9Nx@+P}A`N zE{yc@FyBuMBs>D+^wxBnKten457Kl>Ah9>mvxPq`z`75bD|EX+*rDDvof&9z8TkX!lAgU4aT(uHvrG62E)zr5 zovT>Da7Aomwvmdsh6PMe#7@3*tRk*s0n_@;oo?+)d>Fk9;!^As^xR<59 zr-=JG^xs#+-}uf4ig<{L|0v=SCO%Ze-w5jw<4r1X`th zrika5_(~DaGx41wUS#4YRXtv2!jj7JDtF`x^-rdPLj8+5Lqh$Vsa8V0#cJqk^)|O_ zEBD^zd#OUb&s3UF|6xk@YvGTWYA5%OFqI+HQKmA5`ftJpjHSI$pYy$rLVd|pC!xM( zsYzB+E2@*?+@z>3igSyivK8l6MRil0mny1<;=EE(z3lj8Snyig;`MbRP3@G1Jmtix ziIOr=)3hx4Leq}be+#S`gn%&r#|!r4^_YH=iHY}xS-8usd#g2ZxVKHyQBU&29bP{> znSOnPR+Lsa$L2aA)F4H<>fT_bw%WRfhpKyOkscnd?qw*>k!tW+iW;q!>|GU=XLH!8 zAL0p*Qw#W>>fQv!*;`Q)71dW!g^KF0s7Z>-QPi=D8l*opL4h$QNYmB^O@Fe3hpw%vv%d=xe|5Am@<$E(G_nVSBG7JJlL3MAXiY9TyYDLio z2M#>dRx_d~a+-YsbYn%b*4oMRXJ!=NdQn7CXT~^sgQ7(KMn#GIO?C)P%j9$Ho@ciuLKHY(24N+i5ovmXeU5G#+G;1$+Ck}eag4T? zC~K%EP~l5s>~@)=#BP@>%2QIVP*hM9z0!VE_tt8golGki#dnQ%QZ**X@D9~VdfOS} zmunRzez{I@irubPRJ@Y8OHm1mxcHz|SHC3yq5hq7$E0Gpw)J)SuykLg`aWxv0@Uor!B`78M zRaGm5dQDNH-aqXyF@~%}icDI@vd{@)fY7!g!`%0R`{?CKef6s*9yNa z!&kk%WApMSAQIlSTcZ)H6$pH2cibW(K8_J_Bu2z1b|SH%OwdTL#3d{BtB5>m&yI+6 zzpy+0OGJJZBl2qznL4X*rWO9iP9!#|JV#8C-`Y0-N~97`TLtd!A66n&B-#ovZ7avn zR)F_ezrr)Lp{2DwGpw@`$w0KWXBq-YcUs$PnmVSXy3ol7kUBi|(0V;gqYcwU#Q#~z zD**CcTD?anrZB~Wm3$t+&hCA!Q`_RN&7&puLhaaKLS!UF={~4iUv>E2i z$Bn@qlkpi7KNq$;Cq=KjgemwGo)so{tHEZ2TMgE>I=2egX>|a2BzC*DRBVLEV7ser zj-APCKDEuU+w+}x)usdAN$mSRRB5HmWOsF_lfgOk_vMuWgM5nR`VH>-34x0Vh{~!ABoT45mZf@_r+Ros zqbiM-^7$Qd< zZ$hAL&kLN6|BC}_@&D{VJ^r5)Sc(5j0_(t*Ou3m%8Nq}6+>IIJSZwX-u(vP6-*RO| zKXm(^qRR4J7>fYkPF-)mtIDD6w;+ z4XO8qk%rOH6p$~HsdRHXUK*Q@xuU(j{nl(r$4lKl&C>Bwca%TeIc+a>r}|Tm1N`z9 z((LjU((H=ZG(-Aa*+P8jE%et~H%>nbCqu4*BruKpiW>X4hXyUYJmO1G0ZM3(0g#gf$vwluQq`fKxL4OW|(Qhiik; z0Bn{{X^6CD8lXvw2{j9NT} zA0N|UZ$E_91t&$i+qGHpq`QNgB~Q9r)GT>2T1cLZ*yQ09$TU(Q(?|iD)Y}^gw%Y0t zn_x(}4zcOgstSnu1X&HsYE@qgDAlh#XL$!VqZ!uG<;RTD(&DMDQ*0h`+s?6kElgIY zx+#x$`@PI^|JNp~J=D+tMux={x^mp@vP?D1CSa` zQ(E!;#+-X7!Wd0aDia^JfK!>E)ieoS`+IZlNMYh7fy}wI-fUscofTn?_D?0^j0jF8 z!Z(1H3dF6=nW;duTqPckFt=1C9&ln-yfbC7Zg0N>>nE-f4>-+Nybn0dSG*56k9nX!1puv~WEX)KVLC>wZQBb-$EZ6? z`rtQ}nzvGGK&O7|$S5lbS07)K(379jDW_9pLV!9dZv;&0@w)9c!1 z4d3aD0BGJYw3b?Qga2dw#4bv9Li`>GIRnRTTb=FgB#h0R?DTCmTPHiC{ga#U6;Yz- zgRwSFH$NW-eTvh3rrp5=lU^j_jG{kq|>x-9lPTh^@@Y;oXhMp0V%V0tm8J%2Ka+3Drv0GbAgz88RMRD zun4(FJz=5QZ?@O;-P%iCR7ZZ`Qo{c)phqn+4cGim!{~;Q6U%J6uX$6wyQU>_i*4Td ztu{)@NTU{_VYPaWrcI`vW@6Oa-y0~=usYDGSv0H;4DdUHqhWPmObgg_dS_4{Iknd6 zz{1BgiL;z6Pl*j^=9{8w zSWMd~fpmWl9Qad>z@OSe;7^MU{1$?|BH*5o`+ptefzuJ(#k`!H9m2D*tnQ(#o<4_B zjGPVe(Jv+u>buBr5XM{6~Qg9ZUP_x=N-yYWjHr2nN&%v7x zC3bsyp<5r^MTyoR((9rxLgKODGLj07e2Av>-Zo3a1P`W{%DjL>(R)>DQ~+g+JxmGs-3I`pgy|1F z0GJ(NjrK!|o)N*L^8!yTZc_-;LL}>&W$WE7H2jT+56?tcWBdL-g16XrWHO;CY1r^) z290m}Ec(1ZL=XS%mLK^~kF<^UPZQKB5nS~jy-S|i7`Yq8$urvo|6R|#7-<|G+HY9r zNQ5=o4|ygNS!INftW@SG-^AEwW^?Ws`|x5l=ED%O!218WRWt-t;k^k}ZxW@Y-567H z)fZL(Z>P`J_(RpkF8+$Epn?A4s-UsWp(*}ydko60y)x5ZZn@#guKq>rv8c8y`8#$f zwv$=GmBY8-j9QyT&h`uGhyS=Vvue6c*ZxK=R^nUPJ;sgx!@8Q%In?oi4QBM)|3c*cHLggM$1ndHp~9udOpaC%k|<)opv#}EuJ z|DmB%+vEOE+75~KOJ-{@+D0wHHGdjqrF~qIO}yxei2hXC>H`xySCdds#7 zjIxQ=*EEIc>%+LVxJi3rAqnbMd;4+F`no-K9Qyh&aBCL*N5^b+LT}J&vK7dn7tI1m zmJ=SGI3{sa;^dT(c#GaS);_~tmtv>D0G^eb5@@%7B8n`2_@4PZ%@>Smh%Hx~M;zJq z7-o@Xp`k5+@#(%k@ulD1AUmS#MBARs9k)WOkSQuD+nSs*5Nu%&7kI~#703>&{zgKB zWV{EO9in?}j9pJH(m_jt~qG8CY5;J2z#HePE(DuU$#Dy}+BF zNXAI-ZY3s;OLW3`5mlK84=r-+@tKi9_sX1{(#CEZHzDKHOlpzI1`*RDsyWH45D&s`R3+Mhhv*j1lj1?BT2AWD9()n^lhZVH_Jas6v#4 zjE{*}g!BY4eWXc#j>U1wfN~LDn&R)PBLXIg1tOyS5q^v` z_dE317z`37i%7);V2UUb0j5S`l!TWgE|4IdmV*#i#_N@t#qOKVF~rJ{sb<7#=htnf zpOZ#1i;R+&SskVno*gBq1DN8bEP6{Ho{Jprp~4MCctX6PXkU@N$lX_TSCPBRp0`sN z)a_#2{&SIiqWfocyEyjtY~0>cWFP15QMYHu-kyWocNW>D?w#uPoSpQr-?lCH`@#)H zfk8WQe_N3~*WFfhZIM%N@1(y-X1OmFZsVKV(8`{Lw;-84jL-V`$=;u=uH82Ej*esN zxTE7Rj-%u5%7-R*<=>yr<8bfKf2Pnrj``fJ;~pM|=Oho0d!`UKWfVYfABWeUZda%% z5A^PF_8j+ag+k}%2YAqD3HrP!-@%&Ay-4+1vJ<^NJ&~cOCw@5*eF6G%;y()*`e(ro z>K@RJ!kY>ix~Y&gD%mM7d%8QvUy%=s-JgGVAx;Nz+b)e!z$Fw_&_J%iS>k;CT3W>x3I8fVy$Q z-zR|j`-E4BdS$};d{CS757Ox%#tHEL{L2eKy-)z{P87`{_Ar~{tF>YTT&SVrLJjyU ztO}|B!{h#%kCo@DdMfmKqoZ1r9_K z9qvctE*ww8xp4dr8qSXKTPMJFTPIwRPbis^&SXoRRg-^=e}h=x82|BjckT8o9iI}+ zV7c2T?wuH4YlQ}N4-M+M)7?AqWu?WJ1yJToc+z)sN8zqQpt}n1_M-<2?<4d+^c&?h zIzl&)y8xs6rU0jJAlGR~rvTYg2zn1BGJUY{VWJ;~ETSV)xP7ZeE7Yg+X~>HW;hhlm zc0nw%15#oUvPBF+W?YXDLca0U3kCLEedXZ!6RsJ*kw$-G!3zlJZwN#qupWnnA+ByK za6-Lz>T9w!trv0Qk^&cR_3lKF-iBBOrVjVeh<}bm>i%=&)nkylSC82>My2kqF=vjG z)QxanH4%ch{IZ@&MeC_dp(60(c-q4D{nz8S?s z|7O(BqY2+N=IB^D(^$y!bhrmbJwwdTjCx~~yUxrq_k%HCkBO`0k0f`xUyr#y4}NMI zd;3_NZXf&l*vPHd$x5@8qO&7o(pG|)@5eX|ZFjoglL&m|IrcAOarz5HF#P)1_lSNE zQkec??6*XJ3q^^J2xLJjDNd&;27~z?o(`kF2a#SHYZ`mySQ8`v8yF4$5ja{U)FXLK zWE}-IAy!oK5Ui5tk-Y!p>ExknV&>#|B=5~Ul{{|(_9xG^gMJupB#3{aGdTPe#Z!$?ild|1b;^|aB!7& zzZtYIm*Qhz?)|w4iTiV}f@t@u!H0$r`pu9hMkxFEJ5$#ZfE#l!riK^iUXknIg=I9| zH5l}+!A}pyO#I2>6rG+La%c$ielz6!p}5P+;w~v$qLeKWWu?Md z>T7J4`{3ZahXg}s)?3JzZ-#6hs;|!T9mW@T4>LzXySdDT+TZ`K0WkhuxCgGtavI$q>etkt3Sj@g3_uC~ixPu*iN%~i)+C-A z`Xj$?=)ZH2d`*UNy0N`KLVA1uI|k6CrW#|N$&D8&Cd+-R|9Ac2tNjC>9DvhXIbY@Q z@|suV`v<%~01J$_a^A|})B1sdkz5<~BI`*o1P1SP-yg6$2XpW4oIN?TWOwhP7IWS8 z1Fs%LO9sWRauyWW&p|KlIXSy?0zCv#rNfjQA?(?F1$2JRndAMfrTc;7(! zvXuMtzzYX~oAq`tL{soQL>xoV=;C?|i-ge-=*(R%R9zx$cawq(F)|E^yL@mU z$Oi}hjJ|G{z9gE(P?3{+N0042;oI#!_w|Ht_w_u|Q~CBt&l`IIxUtvfzNo#M`<~lZ z;pg^!mwmCS#uX{kJ)=BOrNnWp1k<91Y-rqTdfw6# zX1%@l!QPy6T0WiK?|Sa-Mb6&a>$6@6*Ux%g+8g7!wD;$IKz-ilTpAsZ^+YvDHA%Sl z^!cd|$?S)5M_t7dx4?vC{v#wA=e?}v9;GG$U-Uh@A61#N(b#F)vo4~KyT0oWU1_50 z`cgM^`%<@8x`BG7+wLAQMrE#IaP8~?9e4KN+mQr=kej-~tT%Pt*Nv>ET1WS*>$~3E z6-K?8x|$6k+#04rO`(X!KlgHUObWh23e3gny>6TUN4tI59rTyozwOR@Sc*($MwGqM zZG8_E6Ao6}{cHEb-ATgX?nk<3)LM;JN31;6rZyqqcJ4U1M>izYk?x;%Z{Nb*q3S+K z|EAl%?zVd138Bw*hs~eszP?Aox;Gw-=g$u)b=)7jUeyh5;IepsS11oFvqHKcfM{An)q(Yc>|Y>QG$8;JUgCMHudZO9w2An2npiX7i*kZ64_I zZWhe)Zq~bicfiltKV+joC}X1MN3%Z8B4d1<^+gsO_C?k=SSoU+-ZvB~Sce>AIA12^1G$jTt#&XxUf2lpJ^iuncoiP55 zozCy1taN^-^_?S2EyYy~uFaib%gwmww^IARv`1w9r9GqiS;PAFx3)*v-3pX)lzUWi zN+5^8oBiMd0Wod_mg{n&{t`6CTq_Ww%$ zq4r<5r#oM_KbIt*+u{8V(CYmTA9N&?w8L4A@}3UQ63eq4{@Fq8z?Giv-cFBFkB@eG zvJ-r9ga)KbiF;S4`#MFo1@{r-Y%u=4Q=EFSQLp8}_h2XIjL=T^LESM?N#RAkG4kDh+=%^o`B^nY!(Ai!1s@2`1~KqFD9eI1v}jrlRrwvtM&`l(VpU?fPk~xHBrDfwcF8-@u>JebmjiF)xlO2Lap9yg+Xl%-x3COOZcfU zsHehj5cLMBSqe-2k__sXpsr8folb$gJN;!M&u(`$nRaKp8`=?iL%Ul*wlEI& zNUJN7@F2+y__tn*6uz?J2Pgx2HUi z0!1HyBWd}^uHm>N#O?aWrPfoM^b*I9)C>PnFe%c+RJR3mnqy%glUvbT8Jq3%A`Lhq2ZsC4kYo@ z9rv@=8_880leZ?jb;x%2xztZm0eq7BRVo7rTMpXWQ(on+A4vaudWc?tuqX7|>HZzw za6mDSHh&;G$eUaLtu-?0Z!{W;ivw*yk%>W(U85-k{HK(QD0wbQy@rCJ#T-(fhI2=b zBc)Y$>r!WAoxj1UPSdV++!0?;S9FImgn?I6eoCR040TC2s86D=NxeChw=+829^e6D ze?!J4P91S%UC>yn*{p~AX41CSWD56hC?9U-&aG%ou`tWwaGi(r;=YtMp=DMJ zkh3=4SC{XwcLg9|SKvJtb@@H_s0+``+lljIiP5^uT*ct}z(rmD0QbP9*OYhI@8jK5 z_kH`ZMEZ)3_DbaJ-eG@>cemYd?W-Jq0(_P8po8|<20iH9=Q4EE-4o=C{lQ-n_~M$x zTOke~*W8o%5MQu7$b!Bl(1cZx!miK-jPhcT^G`z4eI)P~7eRK^{l&v+fAGy9PEWPP2n7u4DA)h?(#F3dg)|9wK> zC+>Gdedituf_go8Fb=1KaqrPs9uEC0gnR!A{Tu@IbLhNyQ0K+p6A$X1_y^*t!w2HO zAo2@3VY428SpuP#CG1WBx;tSnlY0{m6L~n{oueY+-*dXrXl&JWiDgFgc8e$;GS;M6f*W^-q>i7dC(QIV_x+V^!^2WqCD(~IE z&P*F4L3Ar+_iSX@?TNk;&l4%#B6av?9DP#JO;>DgHIHvZww9!+Yt2A$o^g$h+I)?D zldWp=P4*rDY9wGNc4zFNJGveNnk|rCBmgNy-Yt9%$Z@FJ2{Ez?#;J;rIHYQrgo9h$ZTVYsyR+V&L5>KP}J4wlpCi6^*cM($m zmFj+#`faMa2~(ft{+zKP)7_l8B@;W9{h73zyr+|UAGVbA^!n1_?)Kp~52wo)hqmJdLU-tCv%7o5UVtBr z`Ctq_5c__N`x8FW;t%Th(=Yl&>6twD5I!}c%SZCum+_g9`=7ke^DOuI{Fn3bY4G-m z?(O(UnLjS)kG%0PZ{p7r)rYW`qCmqxTy*ijKN8~vO$6*Nf!6#QAhlAdrz#$iJU#FiXQG&K#m{c=(vN(z ziLdxS3qSO#{*oL^8gB<}H{c~Kc5Tm|8QS5w!7oz**v?AZb5gy8pxv+)_uQZp&x}E5 zl?^)V#Fuz(sTW@2wOinYYr)XuB?XCbsXnKq$kj%#4d0pS*}(;&DOp>CPQM*M5}c%i z=&61RU1iZ#-i8QRLRTdW($yDql>_miS5xF*?Wp4vQqxOX;3bR!IaYdI;Kfm|O~J|q zXyD}rQ@su&z1)+%P6xm>joY}vCsMot!4j_@MiU&HLQR4zJvSr8>lt(!LVMA5%2uzZ zvjTtn%SmkT5>E0uI@Nhz+p(a@1i_hJ3c6|Q)RDq%rg&*iT^_E&d0xsq)`o7o5K20_ z!Hueq>0Tnpgc7f<6~N!jF7e_vvf7=93{CLb(-|mEf#5v+RtwIlUNVz5-wgyqDW29!mT2mW{vV*sW4(3!&!C;&Z*XF@GX)L?lBK!rkptBh*Qi9F}>ue0+OKWfN zatH`b^x70dy$Niz0VK)!Z0$jBB3)hN<&#ry_fmuDUUDrihNH*K9B+7ums#m02M2l` z0HovvH*@H`wHC3HhA2TdJ8TXc=bg2A2SX*m`Z!o}d~ZSx(6L@Gmeb_r@p&KXNs@Xqy)o76!WWGx z)MIdymjx0X^b&V<+z5?Q58&p-q}qb{xEjcfg40>oeha;>#|52^2UEzPd*HVo&db&g z-0cY+dyIh+CwQHld-%rC5=v(**w^O4ENH%E0>^~rdnsc}sOiZi==(aa$0+n&D(nS3 z9;aK0SE7Pu{!U0d3%12yn^`>t*UE*xH$y^23YjOk%v%g&^thOGb}nC=w?S$4F$az9 zT%rRBT`G*YrjY(!gB(YFg7z2C0SYQD)ZqWVmRQoAa{?v74K#e`t1~DhDO~tY(7~^~ z1$W>^rwu_T?L8=w;3QHpYNTS6NX4izkFq~;FI`e@Cr-(uORhmpss#NXR3M6yP#)rb z6W5r~I7A)(D5aOR67)jO&$!?d2-$XC%6u>FSg%hxZZwkhyqr8QeFB^4rZc8sU^&h! z0m|;F-arIsT0Jfocq74>GS(Y6-|L-6EmKld9ltg*^3YziMNN1sfMN;mxL{}jmrm#u z290W)CNY_?5jmLp8SHg2E?^v_3z~qDs#y{g20FzXMC|*#B08Pv74Gx~zVEeiN<$4^ zFRnf#r(l#^o1Lt!sF7tkUXs%ZWi9AbGRA`Vh-fZH6?`*QaMJUqwL8!*)yp^*DlOEblEE}VNNZ;qNp{Y)A4h$@!Aqf1 zsyyS|w5~GNImyA-y$sl)-6}6*9(*`8)oW*?%sE}qnKOB-*Um{VL4C{StS^HjsYs>- z_i=|Q!OJ#MwM<9UWuq1#>R{C1#T?3QyzUDCEksne2J9qEL3EPUoS1^5-VaFs zfnGmuoQjUYK;wg>hpB~#v+H!q&}`=|r@>1uZiwjq+Q(S}u!B~B9Nc0LOzvp#27uk=g(hY9m`4_7lHW|)Z_7f`m z?mLaL&vhPmdAf2AoJO-%Pv=pWN@ODf83w5E`sI14W4(SAz!m_b?(Sm`-+h|j!&po_ ziRNm7MgFyeDWMHha&j<(wGOo<3pnvq+*eU?kEi0kDsKl}()w~0R&{bkork7UZP)tB z+VYRu0!ljZkm9T=LFqwW(5kZ&<@iF}O7n8(c^zuK+_PxqhqWhEPV;)+g3Cs%LZRk3 zuit1MG8tZ#HeAS>aaxOO#?;5)h>N96Z6qTgU{%fLX$8xwbgZ<3_KD7^mCL<0A3)%Q z<^_kAj8iEyuj-)AAOAF>XDomJf7X~&8gZAFn_Xhwx_)L{i%68 zM<)lJb36n%JI=|VB{TxMP4Ep~?_nqL9=Xa%+Y6(WfGNHj7NAKMi?jqM0h7fr5H(C} zh`{8&mC#iMEqf}>3(;D8qZiLzEz_Z}Pk?JZ3If|1bP%INg)BQ*;-xzEC~~yL$VGMr zg81`iTnYl{?l!MA0uWzap?MDzq1XC2TrLx{hOXsFAVKYPg4goyCJCHWr&HH+(O1(X z!iJ(Y2|NSr;1};vvL-xEMFmSq41&-pkh+Q4k|82J2mu7pY?T}gErmo}rJ%y3uO>#CM)|^_5AusqihT3-r>H;Uf6JH5>6%}|D@0()yQzbZ$HL7LYM7C=0TVY@+z^nZHY*1^o1#Jpc~1sF`VZHL;&Y@kqtN3w0N zZ?M4YviYFbH`LGTa=+JiUY@q|nuE&DHk%#F1TPVL@(vQ2Lc2w6&%U?fQZG9~E5)9h zXvConydiRep;Rvu!W=ZC0CwuUbUIDrD0YIQ6>59AYeVADL# zi#txOv7o}o&>yRe`_{e3_RI`!_J#!U)2rxzR-QM+8AMy&S?|$RNhOTn)a|7Jlwg5T zuor{Sjuye8C^wL-uT!Rjux7`%&otnIvM zsCXF^^IKIZ3Eqb&ra_HEuM7Pd)N z0ZzvatcC5Q9t=9kA=>95I%s2{9$iqr)nm~Wojkf@@JOCS=Tt%)VbU}&@fch#rR8dx z*S)_NnhcWab*k6B+)JPCb*J1~2-ajTVG}M@dJQ4pRjOcm9H=v@QpM6Ve=L0z+MAP; zL*Z1oLrqP6*D3a~UyBo@R}Lp-tXcaA9XNwgM{d<7lbowHwB^>LA}u z4!-K8q%mkaZQ-N@s(fiU!80hccpO7lbyK#|C8a-}*1%hlZ5)+rTInRfZ)qcOITe>_ zl-^CsoGmN>c$XWjLtzf!l+T@fbRDaQq@Da*p1ywkf1kq@E7Nwnz49ehb?hGnyYL)>aQ&ELQeb+BtD*b zxG3t8#o%5Zd4r7Ko*p`g>69`UL5imY#K#*$r-<0i~sSmzn=!suotMpRP4=Vj8uRmX4f}wMz*PY4Zyv}r{D0O&7 zJvkNnCoc@%1{0;(UI%)7;jF+MHU_KX)I6Fx@p!K-wxe%g7Iah9Ubz&@nN%!g5UpA2 zvDHbm8F8JSQ@nPgXzA*l&`|OULBzq{#?q8rL&-q-EKfSsGIya`<@PKgBkm5_%8Iu0 z_NqJ>E9h(hg!HTAReZyHY?-_~EKQS~jjK0!?P!%nU5XA&Eu@ulnsa?<2POsQmsKT; zNYXEHA*5Q8^W7?_;U5jfiafy0vMGp2)izLd;cuwe(^?bWeP7Q zX+g_bwZRKD{q7!PBS#UE@CjP{J^lKx`OCg{XD1d%O0m`!~Ln&?F~uYV$oY$SWoM#i+=>y1Y(Pd+ZV zC5TE#6}*LIHum*Xrl{4mQ%bY?^c1W$+hD&NzX4nMRaE#c<0ptoCD%l!|BNaL)QFJFcS@fX+TY=6TUookGCH?}n zFSvJ|-4LqG3-oSVIfZ1|&f}u&ftCDF5`p$|d}sstRBAUZ$r3_s=g$Ol9T#%iveiWv zWhtm{3Bi;_BhWX`t3@u}47CR25JLs0o#lzmH*@s#NJrTMjxr<) zo`jFVAQBqbou4*R9m#dh!E+kmNl-O}*@pwINdoYFq4&x8Hng;z4;rHZcm?c&&v3G* zu*{Dd^CBU4CGH^P$Wcxk6k3EF%39FbiSSK!-o{P@#MNd1l9iyAQE-8@an4Rqas53j zj<73Pz@3(sM8Z;6VSFHkaqtHE_scR~8i_{chENuTW=USj4vx*e(&0;uB{`LZ;4_AV zS41$y9RTcq(}uz(RO(8-c&eGGe(?{{Imb(z1tl;yW2T@Fa-9F*&tuR81-tVZTca$x zP}3I0+KCXFQkgR)2MaFSt|(hwTD+Qm53Q)6xODo&lH$_*nMGDbMQv$)MV(c@eDTVP z=ttf4<@I%xMtN+wk(O6-XmYz~oT~0`S!{UabW&VuOfao!&s;@S%`f+pw&z zVsUvXsQ{j$g3_Wnb4rUB%ql9KTR3y_j7c*Im^sIqzIgTA#w8H4y0omOx}joq1737s z(Ij|jT}@+cgVnHlNn_>mit42e%Xqj2GmGYy7MIZIOG~TK2B-4H4U75aF-66tGmA>+ z6wQt$=UNr@Rggp4)m5yt8Y))QR#g)KIg9J-tCm(cz`> zIM!r!yJ1ajg@wOsj5cMbXcJXf)0eEC*+>f2tg411YH_ooe0oiJh4P6tv8rL39!b`F6 zejEnMMb%YhH5Fwml(#85top_!W%&DGwT;V{SCm`03$NF#L|={77ReeMwe=jbb?KZr z7D*q%(o0LpO(Q7)5OXUQS6dYpv7*r`;t{cWWi^e^ zqG)E(G6a`ZUjt*3WQ5W3n(C!lxFqA2*40?EmT@p!`Acf*8VbmyEJj`VA`G%hC(k%8 zf7;~2(m5rih55z#DugVFp=Ah7Ycd>H*RZ6D%(I%Ja#dXwoR5GaTh?1JF>Ayz$f*>` zwBwLz6fYI!S!K%>*Rj52i6u2P$ewE0k|SBEH>YB$FO*g^BF0PW*|f2lK+-vOp|n4C ziJWV#SiG9u3sX>#HrC5f8ms+^l$O>brjf9Vs~ZVkQrEJ8- zR92cP?supbo3Qht*Ee; z@OV|2?y3h{! zvtn_rHLI?syir;}Qt7H1>ZhWvs(NX(Al1uIwK3>Z)oRs%liNKsHq@T~UEtojbj(X2l9Fj$|TEHF68rY_22h5T3DsA-RIIG3hn6#HxZCK= zLDh_cD5@@vuDq_M7Nh45>gq~)7_%hRR45J~icoA=UCN29N>n5Vsa8Q|bUv9(b1tCO zrJQu^6qNSHipkZL%JaG`H=y$Q3vhAWVq`Ykm&TaOHFxgx%H@lf)>{Rz@M28d$W4+@ zK|v8am-3d&l~uQzN;`F|Ymr(F6fJ42FKeu;BTr$Rs1;NrD6}wr&Lv^m=A1%BC9|w) zHA`i}QbQxxH=qNWIf39*Orq*4Rv=!j+VYC!i`PK2vSn6XMLCO)$W(sBO*O;AkQf0? zpWr^Fw31d8Iu7VgU750PWFCmojnbP-2u-=iasi+oYcV%iGa*&gcGi=$uonAcooqV9 zDz9l=vK+OMXYg9KrAqaB%-d`u9q}rC5r7zJY29)@Ew>tJswmI3Dwo%wgZc&(SG3ow zrdfrP%NoEN6CU}~NEtYRrZ0-W!kL(DR#ugva_U8snj^3dV)28mNmX^UkSm;tKxCgT zt!RKAkr6RQ@sD(Zx8~B=ts;(H8s{L69bG-CQ?54+JViqer3qPPxMRPz!eLc;6^^Gf(A7DoE1{Qdeu@ab6x0p*ry;Uv6 zqTZ^kTMS;rUL;X4OjW$7dZ@Jlf{9CSVF4}Ns%mJVT3U5K>f ziA_xmdIF+!OtB0a;XCPRlGE znrg`vt&*`uL;%V%Sm)l%qIyh^$kVyJY%MLNdWYIEqo!dR7MT{hqjg6i2RsF`b~|k| zpjK3Qn!UI{Aof|gnYB6()jWC3jColFxg)Z2b908~49Xg?qM@<6V(iiitmLc8KrXH; zTQ+v}$l(Kq56v2|G;6@j!C3<;XH71^|BLI_RF^HQtEonK4rr`ig&BK5!(x<|dfbGs z%T5_UiGt-qP2GU{1~8(%+$h6hd6~YA$qF~hDt$D9J384TO~pWZ_Qn_&FQ@HGRW&Wy z=1(m^^yrzI-AT)_s`6--V}(prb260^waKCj6gevHh=lrORdDfScna%aM6jwvl*nok zgzZ)ZEMHBDS5uFTk<2OV8nqne8O2(R?5nlvm!S;sO1@4{oKzDmwfLhNQ6F7ZPDH4v z4FG&xTUS+sW%(M+<5VVT*@~gmmRI@rgoB}R*&1wIt%jAQs4Zo*4dr+(Ev0FX7Aa~0 zrK&m?C8}JMP=3?WI%?A7X+@P`xL|SpVl0FU z3wTRPc|j9Cx`^f^Ky<8@t12{kJu!E>tTAv?Ei8Bh!j+Io6ECOlu0X2BSr)8f-A;DXC^wv;?{VDeCvNu47^()x?ytfBK-HKXOsc;Ki ztU`>@(3!*|9sr;QEG@-Uz-pj%J<4ljV<-!E6`7Gm5+>D#nx&NAg$1%3!15ZS)3g2D zqGL*Bg znM~EXp2AQ(!0Lv&Dm-YI92qWGuIMyUO#2Bb>?*g>CoKZ%R@bYC9Xw|vY%MNlJ{J{J zE>J3{+HGOUz=165R;>T()^Hxws!bq04S;*Cfy-)ER17Rzv9eD6H@Bi;R(Z`pRORUd zslJ#0E(exu1EB;KaXDqb%lL;xSpysE>jqX;mo0BBuNa83TRME`faO)yjjIPNt!^C1 zxyLNatCkF0T2|JaB{x?${{ylHk{jxOS8b|wzssPNrD^?l8Efj7ts2NHT~iL9-vtg0x_S!S_rIM+9nmseEsMIAPhTySZnK`gwl9#D@b5*3(xXfRPet|}m9 zjTyaLj))zAf7Q&(6om|tG+LaX7O~?Bo0amql?qz1xQcj0=VewN&;F1!&a#dvC>Wj9 zd)^W}*lCoxbUKw~noosh#6&KoyZ$BIh4Bw~VV-Nj5DV%&!U{W~aLZ>L4b%9$H?tQ*j7#?(1urNGw zW85*}+Q7<$@W}k|p!{&}iQ%k?;f#slexgNxrFt1l!e8noMOYR%E_A?ofjg08 z6_KoCNLFEZE8ao2Ar0~kX@&IHKuO*Kk;lI$^ZW9SA$jec7UfMLc{J7#f8zsxXqL__ zuZZ-U6d5ZzIh$qCS{}an^*dw5NuzwP)cx5%l5~^_-L#m;#1EFwPdnWkOTKd^8N4D8N(!pv!Xn z8^qsuk>4YX>JzmS>oZyTalIWV2ybC|Q~3;F2QJ9NB^p@NqKYsj_jj%I$3IcY{T&+{ zH&MS#4nzDzh$TZz!g(SA6Cl2MRKfzcgJY_2=7STz%8cz^Ny|&7;(%i;$&HRbnx|2FlbP|gmKNQ|c}lr(lE^D3p)Bth8t>#5<99-ROTMC( zzj;u4xYp;tw#3l<&!Q?WC;B&bVwyR>@dsy}n7=$6ZQ@vDwdRLowQ3FmaV-3C;}#Cb zYTXP?0Mb@{1C8(P5f1uE4XWqnfnOJG5g+jzIQfR;-ywqZkHpa5@oY(c*>B+0zk#3n z8~B#rz%Ts`{Mz5ZN$-~Q+z*__bx8h;p7^2ZeK91B-D!%bZK9;!$iwOK)f=u=>HOCR9L^K{v7Nq6e=A|ljtf4VB>PXOpD_IY$Ogr8+c zxae7yj|HUf{_j3~*R3KCNuq#dO;zUR^%aHGPrs^TqYtMlpz+;4oI*|GS@0hnR3<6Z zG~UmLQ=Dsjybq@k)Odvtrx4WmTRuF+hkxh8X$`3PdrAD8`V{zZKmW}>oI+g7nJx1Y z$)Q@N@$-Fnx(|QAhx^C-i4XUWD~|-@pp$=GB|hA*XPpoC+xc!E?$`fWAMV$ae<4Ns z_49X=dPB|p<6Y;&{c`sF2L7-Q_s2hx_B?QXlTOXFUH-Vsm@; z^x=Mc7W#0%JwNf`e)%&)Fboc|kKYbUe7N6_r}=PyzgPKif4~3o;eNjy_2GUwmGP_& z>*M#=dp_K6&+mM=-=0&cF%BA+f4rN0xPQD?`f&ev@Alz-Jzw_W{_%E}d5rq?k9V67 zr=~jZZuH^)xC+Wrh2;3G9rAM#n+p^2KOZmh;r@8o>BIf;e9(vco|E>MRNT5oZ`d%{nq<%zunI9;eNZ_ z<-`4U`>zl8+bz@yir^r9{QdUu;eI{KeYoFlYkat0&X&$Bp5^=P_Nfo|+vjB2H<!e)~M)!~OP&&x-2lw@+6e?(cV;5BJ;WOdsyI&m}(GFXxGDiw`rd zhRZ&QaKC-7@Zls<$Du4Acuhxb^g(Uupjt~j#|c1Vr-N`>GKg4S^Z0SPrh|V14`*7A z%Tk(Pt1thrHbP@cYCe+YS6t3KkruBI*LiaK7 zF+vYFaQ)nSvVmVC`pq+N{hVTnfj=SpiW&p|ga(4+bOXOu=yMEwfyCS8241AZSi1~7 zUF?5{f%g=<-EZI};@_7HytUAW4cwD>`^3PLB%bvjb<+NNN%E_m#GS_fBXT+#cxTbG zw}Eev@eVccDg z7a8~f(SMtPZllpVe!28K_9R2x>j+1eMKW6a1AoSA){);FlZtPZH06G4Ov2{h)!* z6+7tXjoNNs$#~x|_?HU*#|FNa8shlQz_SHUlDO0IuNV9DGVs?#|Dgu1>rTFb>-EhH z1OHCq=Xe8OFL}4jzaQ(*|pEB?-WL$?0e45BVYT&PkoMw1HV@CXs>}kDst{I@Xcb+#|&IQAAiZf-_`3A1X)o9=gWn5<( z_;X4fORulBo;^gL9R~kyvGZO7m&Z!hK?DE0==r39pDgq8>jvII^!(VszZO6KVBq@w zfCSM?>;J3J?G3z#$nR_5xia3-27b2qYl?wiC2|%T_%z`^*}&^0uTM4b-ICYm8u-6O z{?!IPRN~=Y179Nce9FM}{_0-_-dXxRV&KW*uOAJ3i0Dth$4&>`CX?MP+{l798RR`+|cg{R+-zj$3Y~b>7fOWBf zKP~#~H1M`E-n|BXmgsqpfiDs}JZ9jFMV}W9{9?fm8+d22+ff5Am+}5!;HOEyaZ-1+ zJwKOmr5gAIX`gN26D8h88TkDoXOe-(iJo%}yqCn;Nd|sI<`Ml|MC+L;{yo*;*Uvl7 zHt==Q@1+L*h}4to41Bfl-)7)PrQdrE{A|(xaRbj6zrSqYw}^e-G4Ov&UjNs?6NLXK z1OL1DH(ugS+vh6LC*8nLl5ur2@ZqB8AOjyS>#?y0K0)-DV&K}}^9_8O@Ru9-9Kru& z;8#i=UT5IjM9&Kh{3{vnHUkd}|1Ac-NbI)Xz+V#k|J}fsikue>{8aJF+Xnu<$p5c_ zUn%zg*}yZz4vErE`*E-In`z)nP#fv!W#A*kZu)tZmUDvSZ-K#|Dg3hy{A8g^4ZNp} zcZGpZ6#O&;uM)qUW8jry=PL}nK=irMz+V&mP6OBb6a6{69`9_))8`ES$Hfl+Ht=my z2aXu{3sQfyKec>U_~OJr8lNQdb((>fihsKq_$8ueu7U3r`;0g6=cN8mH}G7s^YI4$ zrSMl7_?t4`RR->goXrM4SmO2)1HWDTew~3&Q{iddZs426ZrL(EZMSY>hXDqDtniO9 z@Xa!=$p)_HQ@xMT^7VXLVespDu-?G+Jb0#o>$-iBf$O?-je+ZY++*N6AMY{n`^EnM zFz~GsS1%j5o|pCWPCedlgzs~MKTYiUtAXDud66Xk)N(2X?_l8CzkLk+J6R8mFz^); z4@CxEDC3=L;G>0qgMq&*_=N^OK;mbcf$RP4qXw>@%jxIqdc69%++l-%q2$Y_20lyb z(oY6{z1TTX=1(pEQK={G4EzJhlkNttpDPYF@MFa<6AXO6=sCl{drQBI47^10`eXxN zA#&Cl_#*Mwxd#5N#OD2L}Eh$(OGT z{A97eLobWq(0&Pt{;dtXPU>1m10N&y>1*Iq#LklpJXzwS*ueF;mKeC+Pu3dv28sXM z4LnWc-)rDc%DA2|aQ*#(R}Eahhw;9F|4qi_(#tnKyXof|$p&5|c{IYnBl|c5&z1a| zW8hbboKp>aki^MV2L3n6_ZtmduSY*I@CLmOl=uB~Jm@<4pn>bU|BQj_`v1Ct|0H^T zXy9jwU%oN$$CY_4R~{5-eOifqrWyDWk+ayqwVu@meo*Xrnt^LQ&oS_OB;Kwta6Mn% zXy97U*A0B4Q@_B_VGua)|_+`vB;zicq@y{e9a>wd41b!jgjPN^tm*y?BC5R-q3kIV)s6Fw=*VoLRy@*TAO;UgE<^&SV)tnGYv9 zHNwBtz^epb?!!q=qu6eE;e*L{XTi#DL3fHCbT{xH6m0eJ;ePuJ_2DEZRq}eIfhP+-)`ycE9dF0_aFWwk_@^0o zPr+yVaFV0rZIKTrIWvU+Bm>VAyuyc*oS&!>j#?j1kp3K@!N9K~5pBa_2Ja7en0OH179Ta@Aly&C$etw;Uwn* z;eXV?&k_6u1HVb|R}6f+;Q#XB)bB*G+Ytl5O2+$*4<~)z5IH{>xc*!zLEfv^dC^+x zZP>sQ1#c%fbw{E*X5&8`**<-W;88n}K> zYo>u85;+S5*ZQv){mXp(q<=Os;LzV+(Dvyj^WZ8UKaDF}@bv~hK=4fl-cRrg419#( zO$I(p@T(2HNbns7o-g=r1J~c%++*PSy{kKXxZe&B3vSx&X@fsk?Dj7oPWD+M`n+Y} zCkg(cfmaLuiGeQ{{A&Z(?`eE*;H!lHSHZRZ&t#K#5xx66L3ojYZxs9l13ypj zMg!j|_yq?355ccC@T4rI2Yw=$53Ns6!T)XG69rF@@1bb^&4L#g_!WX5Z{VZ5T6|Os zuIJN6S?8=V_zy`P*=q3Xb=o!`PJX{k?7!2%FA)3&A5Qg6&%d|(aFX*k;lI3hZAeP z@c-AqmkR!?f&W!-N8(7=r5glqW8l9Co@U_R2;Sa@lb(8B>*2$RRlj%9$H22CPI7!W z$|Sj@DmJN$LC@L|C8|R?_ue@_(1Zf!N*UmdL6jQhm$^+ zh@343ezxFE2L8I>ml^nzg6}Z!ZnBTN-oQHuzE^Oqzs~2s`uIuoC-_IlV?LbpZxA_8 z8hD-HFB-UhPw!O&KVA6WH}G==|H#1g_nW>j@XLh%8w0;Y@E?7+e_XmQns#WHMWa^F z6^Z{_rYx&xmb%b+q2JHzYv9*Nze5askKiK={1(9{82ACf3l01(!KVnW^*>+od8vUf zl=}BLn|Z@bqkp53Oe_$(I}h?<4pL2EI)2%?5sk z;MW-V7h;D!g6nywi>z<|YVhm7-=gPj&9CR3*L-p){`Kd-Zy5NK;+MC5xWDdw;=@Uf zesA(K1OGzgeC@+Yj-Ge)7ZPh~sHnQt}Ti5lUE_u&NTc_-C}lRiU*Kf}NW3*O0x z6Qt*z-aee<94q|&4LncqTpv!5uIKp%uIu?UA5QZ1`_lTkjvntyk$;lGuixV=H}KuU zUuWP0@{^G-FNbAWU zIPUl1WVfG0&O-+Nh2W3*aFTP0;LrPTlG9J*zii<8dkXIxc!}_TWZ=buf9k`j-%q69 z?|nG+yHfanHgNquWw0BVaM<`4z`qye`A)Ln)ST-1|3}-Gz(-YN{odQ%Nt$fkgndsS zK*DAsi0q^xk&YxNtH>hA5+~!vXh3F$C6a`2CpHkpeZh6cUEByDVnA73P()N1R1h2` zqKF`fpyvJ0sj5zOCCtqCzW44Ay6*YcsZ*y;ojSGL+nr_jPgk;rll;e}{8R&fRN_4i z{CSCI8n}91Jix#e{}2OL{38r}y_7%Fz^f!a&cL@ye4>HBDe6*i>kVAJ|8TR!mHscPeWii_RX#6SZ{T-G{6hnOOyV^LzDeSCXJHUk z??H(t8hB&*+@imM=SzIFffq`Ak%2!a@n;PDJ&C_*;O|R2d?<19H;T8{$awpi!M{sB zXFX}~t98-u8Xg5XlVpFnx(GWce;y<87!9X-)qXHZ!%0rDnSroaFpnuHQy!ILY}!@{cia^}Vne z2JVs1DGChycPZzu8cy|oMvjN8Qp2gp&z1Om1K%w1#|*rKj00;WuEyPyq%5w@2EY3K$-M@@8h3{^oJ91M z&x^k?@QxDyPQ%G=YTW&-;Uwoa$$!qkmrC490~0Q#zZx%b8cuT5d(}Pze?rP>s^MPn ztMSr7!%5CNlE0IIZ;*Iz18*$%cNZJD`kjY74fjI68ZS3#IMu7Z4>HfdlcoF$4JSFu zpYPIek~3TK-)G=gOMJD4lN{yGPir{IDVF@t8+eh#UzWJ?+jjDOsUrq{fqWk*B^^R> zDgN~mA8Fv5W&FQV;;P@(`>C@GeoMZW^;d&m`R!5-r*^$2_5YiJtM|EAXt)>r%5U$} zaFX+-l=F~*@0a*v8cuT5eDS=7lN|ohDI_l$_*p5ZO2bKx`aahd4JSDrWPICZ;H@P7 zu7;DG%VZq;M8iqWFvPB3+Y!)4csU3RvJ!n)c8u(aFR1v@~0X2#S-tS;Uq_m zi+&nTawbXsK?XiS;sFgOIci+wX*kLGljOhLz^|3~cnv2xYFx}PaOLOMYdFcTlyd%L z;Eu%SX*kKRh{gx5N)0DDuS)(q4g5uk->2auNBRE~8cuR{NdBh`{4I$;ui+#|{m$>( z8cuT5_sDh{_&zD;kcN|-X)>OiFmN?qe$#N0e^$z|dw{@2{z>?E5|5U+8ZYYmQ$r10 zjk~`X_yE;j1Ak89hYehPpW#P|tMNj*(eoaD@u{Cy34s>BCsILY~!#4pisl2a`4OAUO1#4pouk~5|eK5$La zaFTPEc2aCPo| zP{T=|0xAD116SYQ_{qR4CI1-%4@vwt4JUoJ(*VKc?Ilt*exH*3@dmDb*R`3%)wq5| zu9rG!{1hS=3bED&8cyv}-|OgY;BUx!2O0SH63;U5ZzP^;;Em*ZIN!iyBtAysN`LkJ z**_cj_yo~_M-BXs65n9pH%WZAfmcZUTLWJ&@t+O+kiP%dx4F2jkfyZZvRPn30+CsxgL>t*JZ4A7L z#FI6g{8p`7x@$Pe2}u4641A!(`)D}HiI2wzuAv%Ea;}p6!wr0r#Df}6a#UO$tKlSP zp5(v6z~@MOwt?R*@j?S%A@LhDoa$Ba@Xs1f^{$uv3k|$V;voayDe+1J-z@RFG@R-^ z?#2hMM>L%3{Z;ZmVc=&Z{k zv!$HR4SbNqzc=t}B>tm;&y@ICiK}t?lJqN2CMv^4{!j7ndLnQ&)o{}1HYum2fiIJI zih=(_;++lrF^Tsw@C_2b(7@M9{9=hK{i~$@R~dM$#FrU(2Z=vy;9VuY&cFvse3yYQ zl=$Zc{*J`G7qa=n=NKvuq)1%FnLO#AJq&*J_hp6~{3X*kL0A^DdZ zc$&l?H1J^(f5gCtNc`^_PW7sI_@ahWy?>PauNwF`iSN*GlB2$FyVt-~+&iq{B!91z z^NoS4=NjK>ILTM*z@Igo9f<1 z4_w!3IMo}H{5KkSxx|Y!oaCr^VX=mjoHr$ZiGe>S@nsrLa@=ygbccqMoDBKA>}~^Z zD)GM?`12Bf+Q9!V@s}m8;-~suyw^2;ivJ_ay~Th&n5m(1OHLtUl{lg z692})f0Ou813xG6QxaGDtKVO%)%Z#OF{C^$UmuYw|BsRHT{JOp^}VdN2HsfmCmXm= z;^_vSBJrLE-bUgVN?hqbmO3BTl?Gl)3geoo$)R>lWfbqvYB=?a`X1LU2ClyMRc7F0 zq&|*;kCOOui7P!Fsn1geoUYF$HSnk8erlD(NewoE zTo6yVRp(kCX*kKLlyW{baP|Gb&orFm zsPBV+t>Gl+c`4^x1AkiLKWI3~`Hl+V`bEPD`atskX5eo~JiZ?n3(wWn8cy;@NjYN-e3-Nl&$|`_{nKy6z_pC;e^t{_`0F|3S)+=?^Ac6gP-}wB(O7@DURCYdGnn)^)8l zoaC&M{Ot{Vxx_nZILT4#v7Q=Ea<)kROap&K;sYeE<~8;E-q{*IO@YnvpRTbQ&i0Yt z2e`t(&q?{y4P1R+WtM?=l5(y$@JxyS$-pm=_+KTi^e>V7)3Wp6j~H?WOF2&%_&|w2 zZ{Q;&{*r;`Nc=SeA1m=U41Bc2-;%h}f2Gv_Uk1KIK1cpRlSBQI!o7m$s~S%IqP}nA z9v~Q$f2!~2#2dK!zK!3&Z<2bpGVo%Fw=?iX67MK+)vn!A|KSGy7g82iz9xs-)q_#2 zA2pm<|0d;MZQ$zrKYujvyCnY&27bH5Z!z!}B>q9)7if|0?@suYs$->v=%ps$WFw zE$g(wuk^Hr2o4qBVv_~!F>sf}8)-Pro2p$+G@MMiw&rC>F+Vmi+k! zzChyYJWsW^OyXA<{P#$Fih(~Y@v9B|DTx;t_-his*1$iM_#6X2DDk-l{)5E-V&K0? ze6fMY%XnUD;7ug%7kM4^&qf1R z&$HrXyOkXE{C%QXy6Ppj9>f4;ux>KRCjZW8Gq)ZI<9ue-We(d_v}RFOy`Hr!H6VP>IWuH?MS5h)7j@R48>5bw!o} zWP1EfadRY2q|J~}Ju-}2Ed^{*{3KkD!IbrrQ7>V1N%s^4K6~Il$^5@9Pw~~12`F6u zL^*#DMwiJ;kE0uE|K1On-SW!7MeV2ch1vdLz)YD`GH(Wb;`isMkan58mm|-_i2bT- zM4_jXMXEkcUUDN7BVO+^Q6fVm;ZKuyI&vCn|G}YxQ)j_rIx$HC zGEw!b^kLu)mG87p6v&qK%i`g-s`6{jQ~sI_qJ&y+oUiZZRzpXgT^Sm^wa)7f98CM2<$V=Rd&%S;7Fxs#Gg?+`{^w`BYd6&5CI*B$mqjgkW z#Tn|4@c_zVI17L0x|Nmhyp!j9 zgj1B7=3D#fg!k}m@o}jahepR_Eh|dx>#KTo5VALa>&|oXQu`Hvt&gvi){~cpn(m}( z@|`!G_cQnBrN5JZ?JomzGQZEs{3dOt4awXZms=V#Z3IhE< zyrmfgZ&!i?p{9)?dBJlPcTPl|feLroZeQ7E)B!Da65?Cy3M^at5pF}-8MT3Bu0b*L zynzz89VqVR4fv|uCDnF8pgS{@>K}?iNxrIT8|DctyK4`Sk}Y;%SyJ&?t!zP{WD_x4 zzOwE343@k;ovG3uu)OTDVi?N~1!A1{H=l72ggD=dHNV%^1`D%O`%w@3)}sHimc0@~ zeHtuzD>!*-U`k+W;Htp1Hv>3fg!QZKfa4Bqb5Yy$Hu0`=Z>?<*ut zc&xk%Fy&QHrZiKq0t~*gJ+MZgf{hcs^13)*LVRm*4`6_l)_(PkAuav$H?Wjx&SQu`KN=3Bct7>Ykd4GO0Jh}-4d zf5A|xml{v)&aTLdNysUCyQpKyHap9=b{jJ7x=h$3r>uJJVIH?Miu%c+j*$+RxdHVG zZ#m-jmC|4OhOWD5%x3P#s11fZA5-OO*kTm3c|1$R0B)(;UpIa||NZ#&{=XQ%W_|yI z@jC~Nm*ck@jnsYp|M&RahWg-`^Qqf?rQK;@iq81Y;}?#x?6&_$!#wuR<852oIyoY z%S-l2Q)0iYTzCk9<3$=A&D7$DV=Wagi353Q>r}3U^@27C+RM1?r9*owl zhmK>XAJ|qxQ;roVv227vcLE}41!Bi127=eM!W z$)J5Uct3(V4l?1tq`Rw@eB+ zzn0Y&4bLz9p2X$Z-xoN%j)6i?Q2AhmC^jqDUC8m3Z3AQQ*0-oGt>;#jz3nSKj*1ZQ zj;=vJR)p3;d7#Vx3AEy+ERq%~^JB5YeQ>lTTqzq^6gQ;*2fpPmg0W41)&Uoj35-fb1ONX)(V76t#dh4k5xrJMW+#>}J;{^`m zHCxdmvYuS$>&)u)OQ40XYJZ;dbFgqfmC18XWIMl~3_b%T?W5ai*8o*UVAB18-6s4az)v!JZHsCh|s3|1ipPU%Oa^S06^q^c(f7YUY}_0HYRrW3O3BsrUD z-27)b?m9U&v1kD+qUIwtp4p)=52sbEpbn&2(5HD(HkJn!@N3L)S-e}J9-e^)2R7ov z3cOCAd7+|I|IzNEe+%Cvdtj|P{yppniaG>J-m-IiRo`U~e2yyctw^Hm1*tv^pwH*- zT3=k+-r7Jrq-^JyY9j*w@%Gp`6#47r2=VaKsELptgb}@>VC}A;$iq}jiy)ekn^GFI zkDM-U4Q9{WVR?dORA@nz6*yf^C{{exs2-~*f97u4o9My<=MM6W0;hyLqoyw}W#GSg zp}f?lr;A6SWO7l5Jm>Aq-TBUOp+cS$pDKezd@9xyjq{uz^PG3IhEXk8cQr-ks+D=m;z_lARee$i zzAEf|p|W${%&+I}BGW#3uC`W|3x=jdVOjN>4BA!C&@yXoDv&bVVEud@N=x2?l8sRz zfzmId=N5iOJsfz$?*(z~TLWONywEFK?*vOhFXdm5M1WP4RZ(#-Zk!znv_S-XH^Fmb zoE@EOYk#kGN+_32cov3|t5`@z2opmmP_o?{EP2zLTe8g?xV1J5d2=7hE!-o9>bJhC zCDcO&C1+yh#f$OeETQ|;NF;|TndJG-vuY65j}0(G8>Y)oFLl+C!oixzOnniu~QKXt|i+LtrG z>MvjE3>2)nStu>`hwPW|H|2@%a0&9Y(6E@zN8Fg-%E|KNfuUf`<{Cm)tft1t{3c^V zL0$K)Jn3tHcKn9oKicSr-!e~cFDIv>D;RpCA4;+KVCeN7fSn_mr-LQmoY`Pu;03pr zRV&u*Wi11uRQj3FvSi$6R-aCyAAT1sWnuyre1;2FY~lC zG5XmWyTOelh$TtMcj&?JM2PJXf^z7()M(85sZrbTUud6sdL5lmQUkYRVJz+(dX4 z)~?}uuhaxzcnI|n!LD$Du7mrk2*-d@se7Gei>&A7Xq zKBmKu8F|JPmb%Fm*4iD)6ZpH`l4&K&N53&i0_=5wSAY*(*3QPp|i)_y#&M zQVr5GGdL4x`3+?zb0*5GOd%7kfLQX54fIF9ky)p$E}Qh;;1?Wh(ZG80<$UK%=4rGd zFSVEG`V7G$gbT(Ebacv$T0=MV5sWB1F144GNc(e5A@bG;g#xdUJJu6L4hcSpnsPo- zbowquxf-ra!{Vfs<)Xd^X-C?-)!*uR&5qJ8!)=5kP^am zf@Wb1=2du7g=K|ony9cOJ}x?hPM{%=IdSTh+6x8-t#^e>7zb(%QE}@uTsEsHW?M-f zrg7-=`v$Mjy=1G+q5>6VWF6G=TcH1T-|}M!wV0QeKZe1|PjlH`w4QU0ZJ^N)-Eb(! zWr5IPDv*Fv9h|wtel%{#zGqB+0oEk_%QgC>Ne~KDo4hZb
@#Bt`JVn(TT0@sQ_Mu0e}3YA@jzh@%G zh46g@Gk-W<=Fa>gukbMSYQFs)4(v9j@>0rI<|cP5JV!ZV0C`2D^X(rnX8ytM>wL{R zZt#OC+xZ$g7i>P_V)l}QIMpm~2OXKk*-~)pAsia?Yzg>QZ6`WD$2migcP@@i zcet=ZvIS!Xp~-}9w~^y1=xkLN47Q5Hh!=GUwP=fiy0e$bIE=lmCpEJ+;B1!b=sf#K z_OfN|p-J|#bsabU+7*G!A_~a7yHLe zb^FIn;r%0X-AsaKVr9RDWTR0yKcv6o@wkT~R_9j^cD}7aKXz_<_D;B?-y#>yT3qb;1j06-V6>DzA zr}H9x2K%3x`>yo(+)#Ei81o})Fq)BoS}_Qg{f%xwCn8z)H~IwOV}d??Ym5CvETPXj zAHdlw^`2EUA9uVtnAA~BO~9wSD2PwbTzAPfmviFh-36P!^--qp?#-ni z(wWH_dv0@SNGz92n~~$!#ACi(S#rdNLoa@~=6BSIdT_RUbR(t5?4z$B-OxUAJUE-# z|C>Q;IW*8LHBoRgb3;|^HWY!LXIDMOE0=8Ngc?+|g!F6XV3AuOGBybC8w#X-&5?EH+jC_HzZm6F{ou(n@tAq)=1cxty}UUP z?kbiqT%u+!T8uC-7^C$&JcCq%FUKd`NwVjiB!m2nyxohY<`T2fi9gj9NMHFOM(_K^`O=qZlP2fYOWH$y55 zaj}e8YQDz+Dp;w!sG@8Jd@D!~tu6~~9Lzn+XCT?m$$aPc0;fl69zCbZbG9O;IZLRE z$s_-OQbl>g;z77Q!dIFt^Ukhct*y6pZi z*6gT-JV zbP1_+`t|qGCQL3vb3?Z!E(lTkw&97u3J*%=IKO50|K3+}7iy%!%aPJ{9_xfzIE#h# zeO3IN07utka=7a@Jkj$kgcv-GELZE}Ksvoq5WuulwmaZk{g#uKV;=-kwx6iat>;wj;1FQI25NEbIQK=m0s^fpOw}6N@wAQxpSQN zz>7!ytGD2JPJz9Wie&D_26OcmP*hTMehnR7twh&TmnLVvomaSv{Ed3m8+5k8)uw~M z5XY+$E$C5pTE!CfH|KBUZeW|WeTnima)l!qHNwyQuv1HVq@7&nW?gXf;amGIya^VB zEcUXu$;$%lS~;1ubYxUmlAYz8|aKZicrk=7C7(L?dZ-on3{AZGZgcJ}|! zx8iD)%PIY8?q~AJ0=y}Xy{TwFYV^QqeQ)ff^SSAC05uB-MBFNm$oygI0(tpTyy5XUN2SXxS)Rn#iN4Dfzw|r7C z%n>p~4tvj9j5hKj`il`5X(HdQxPyX(bG+src#4cy7&q^;Az(g?rCI1@e-6e|?#!AT z=WNjhp@6VXAat2cE+X`5l-<9^S5AM20U;#dd_X1gX6~dt4mzCN&e<8XfA9W0Iar?a z5T9l~{}u>1{Zeu_q-(zw>0RF;E#2=cU4u;w9DVsm=y>T{(r@5MKZYY1IZpH0ezok2|CL{**9A1;SM+yY$gk!g{U7=j{S*NeJ;+5P z{fYv`f9+Qp4f_?z(EMsSq7wPlQl#uxl;5CV8SWHzGhbP2I)}s%@a7htpl-`A+%4i1 zqSmFMS2%Fz+IVQOpvn`dc$%Fi$5}#76U4!S{dvIs61i2b^YmUM&d&5x0q5ymxFz3- zheX8gV+a6ga+!Y^zJ>>B;+a}LO?7WM*CFORza8y}^$|a{EZ&$P;!o260uTgn1X@j# z#s0Y*ZUP)^Faq+N({#!{{9tV@S~1J{FgyKA#EkxfX-1tlG&mE%l4j>**gl400a{Y# z@!=57kGW~cEf?{7PJ1wF#FHpG5%=R2EDE_tZ^fD=;A|H;HFNkWRxz}|INKw^$pGut zL;220%v{tw=QBBe)ADBKQS*MFQOV;3uK@7?DLhS<#v4pt(hCC=s+D`xkgQlrw$83t zO18|it8vGGTLOE7&eAh|VeuW=>DA6sddN`GLVPko$b1qK>gQMNT>&2jW z7F@j1E$rP=+WXYNmnk?cUL2fxjDr5Bk`aD{_HA_2`OXnCL`8|%sTN&SCyJJYR#Bs2 z#ihN!h7da_p2H)!cBiFq!7#hD_gBa`dOPiReinNS{Y7a+@&qZG)TxUHQ$`Ve4FR2o(|QV7x;PC5Zc@DbFA!&azgT)ZSE@a*2_Zi z?sPo0Trr=xSI~pBJbQDVy)Wpj*aaPH)^kk8J5hEZbc>vk;BDFckIl;xLAO8mWsb8g zSol75CssL)s2?%5b_C#5=*$%!!K9t*oCJ3kGN&NTX2WKK%2Fdm z4iT&#%VAP}1qJQPrgdWXV(QQCYv>b0XBjV^)`&5)ZDgp2JJ0?CD?4(L?Zq_3(Us@i zLvvuzd6HsQ&{;zRsciS6TR2AXqWfqkI`Sq9Oqv_#Dg@nJlEL|Qv}HX5n-W$#-d@j#Zb!- z{)VFevbwLJ_DXNZyFzi!3i9uuQ%=>wblKg@iNh%;*ACh{^6-w1Q%0Z9EBOh(|&2xintc@WM40J3`@Nn6$7L+n?hc z4vHmBj`LlP^D#PFtZh;$KJ6s`2o!Hj6b{mi&!l}-KlK0Dw;~!rgmz*3=9y6)`9tMwqd{LUUQoC84G%fm_e=Y<6+pOe~dY-1VbLCL=$z zY5;|t5zef;JQz}V`)SB6Nm!#|8Hm@a=zZ)A#D+8(fGd`fT>{R>*oNYWhD;TxxQpU^ zp8a;NeOoV%d;@7hQ~Rs%8uh1>G=@=ld(Ev{ydt_45ARYMS5}z6Ek_pcZ>?!^LW^(M zHFI>x4qb`}iI+ww7$Fi>pqqsJZ0ENA&Z8h*H~Wj{^?@sB@<9HFxlS!!ZF4^1$9(jDJz~Ju6n>F~)xkUIsm(=W3bOlu zTNIB24m^~PDk$4slumA>zveL!zDlooWINRnXC+qV+pOcJ+FA;ZqMH#YGgL(T`Mnb=;4a5Y$Q0KEj(+dGJ)2tY=?URk3VZxp~G3WJnwRLp^tuNFA7Y+?dbqsCgu0O z`527XIC$5AU*bqZ{iQO=AH$1RKcw>a0Jqelz0D~yEi_Y$gT8l2-715<) zJmy}zZ9pEvKuetQyiRY}<9hRH0vBRM^Ig8O-_W37 z$!n4C(uHj&4Uc!}+!HbV$#>}}yLK1eW2NS!UVfqI053x8R-k&lSUKS(t1r0fK=CGe z&6ggW@A8!{qUzDcmJ}gTmQ8TKxm}ExcOko^6JQvmb6v z*#U3YZDD9@3PDMS@zIsORNq1Jw-fr5`pb&;$Mcua1ktP+ z^^4a^`W8u7Vrn5;DhkH*_D@j-V=n4M_d-fcUnWqn4=}DV=3+P9bHOxkbMIkQ(AzSG zS}zKETluNo9c8?)#_w6*E z_DNTV@eWDXhwqac=Z5c7lKzbQ7Q(VEgiX1Igdslq{jj(-l#QEC)b8)2tn3&Mbwxsn zJpm|>4)G}lfQu0~e|q?nUSEVaiBBty`_V2|Wn&p{&!1`hna-aV3eG0>4#g?t2!8QP zfQii6ls}vEXDj|}&7Up#v#pRb$IittVMKh!@V%~E5)V>K3<+B#Zjty!k}Q%8k@OKs zmPjm@DDLLOE0TDTB#0zYBz}<$^N0dboVX$qkD$CFv7(v9FOp=DWQfG_iuy$27fDNz zM2RFpB#9#NiNtEehnevz~kNwP@NMAAbf86xQ;l46nEDw35Vc~~TmHxeCC zW#1jy0m5B$M~UJtiRMqedm1q!IzG;7M&6Y0Cimw>-hFn=amd9#fcx#n8{w^z`pou| z@4A}L$g~nZ7lga{Eg-%Se4ggFTxca65Ja?<<4xFOdlF!mOIX^ccEaN*KjJYGeo@*s zyV0MJCu(}zPB=$Qf_m4EzYUyzLG85TNDJ|0^@I-u)l}qtXvdOH%?0(5?I)wO3{n%? z%9b6nNrk>yiOr88<#?6YsuA5wqD^9K3|v{{w2SQkLIjD#4hg9sT8f+$W=j@CCuU0% zL}w;41kp9_AQb9nB`re*JlIJ(2RWY_@<}IAdmJ%PWg^c?^5H(|EDCeDNKA7t1Xub! zvlF|oM2?W&Gcg_alIX?Xqt6mK8Sx}En{uth3%w*Pb(*01Ff~h3edDQ)$EV3^E_Po8 zuJnHICJv~CUkG7?^lAp{)eP0E2}D#gJffN$S&dL(1j|@8TTr(3GZ@YDrze)DcNdWXk`pl|*(G#=g@1 z0PZ`J@%+9HZ7&4Mmhh#-?SLRjbYw>qq21Ssm5CN?sZ7KOqBFAzhw^pdoLG_5m5De( zq%k2TT3uP>wR&0*axWHzZ=ZzfzqKp)oHBL!UK9)dF5 zP~R|L1#Z|cAHWTJBpwf?&)?v?lYJOW#6Pa2msVhaESXufe#l4AQDCp%>fu zqdHLNJjG3as9xOP(Do?qbEz%Kc%JAxXM6vGWj5jw8fUu_??P9KpTlvx5+B8@x&7Xy zUk72|K9lb|Olx`WH_#@nP* zrpx>kS7RS$5_)Bm_3PwnQigB;&`VKFcXs)y%jbi>G-p(<q@HtF~b+WI51&BGs7hLA=PC{ZkMx zGx3=qUS;A7K~ynuP!Q{xI3$SISlU;Dc%5DUUxIjpbG{bDW+uK7#G6bU5yVy|Y6P*J zMSm-Zw>jrKLA=Yv_k!3NOY4*4g7|=mp9JwC6K4eR5fi_P$nr50mW(W)az!3V?PDrh zQu~?HE2+X5|s)nf+k~+#%OG$mtR4YmS z5PJ_=+FDXSa$Z|Won)$=q)su_UQ$1EPjryfFR_1wn90#y8N&UuD4@uQ>i!$s4 z8i=;d-q}x3Zn4(O5>%AU<+248BRGSCY9u%>7gU_!94Dv*!8uV-KEXL%P>luWEI~C9 zoPQ8hGr>7WP&jj-*{w)Wtpw+MLA4Q_#e!-lI7AG}mgI)EsfL`-pq zk-J!Nr-{|DFU_Xus0*^JHBsGde>Dz8#pKk><~Sj#3_*EB-i1PKaWdlTBl5)Z+}Bs+ zwG^EFMB|eMH9#!ZI|^!$&2ERUf8p{C5li%(3*N~kHl?sA*QpY*yhcHAy7O0OFyMDB5Mxe0b-n(*$=X&&^vlD7lKt|C;FNZIR8WnC%*O;3E2zf>CHLLFC+tdS z!sD@d{FAo-wy-KsM>PByK}l7fjZoz|JC2G)v%fws2@g{*NXpC98awVmVvHBQOZU9` zuC)d58~_<>a0gd>(Qbnqw$qWfZGSpeh@utmidIPKJwe&c3un!@d^_z}Vw9@BZ^!lr zCBp-D@42Av5mI&uYMr34m#1=jMcku8tc%ZzqlF$7Q#3_7TCGZ2=(^ipj~ljRM(oG7 z{~<64!Jmq_BdNWDlIrcV6N%Al#nE8|Es0rZmUNVV+Oq+%0b;+f+a3qe{07T+P&U8$ z4RbRs-3phVdW3={n zQEOc^squ%E)Dj@)(oUdml5>$iSV?^W_HAi5t3=8xGrI*(-;!v_gjmMgt&(Vp6f1YT zHJjBhc5HSV_c#cMBPsOvyrgD%RE6Cx{y{*EaAGPh5SBI3<)vmH0io#>)?NJirhi5L-F*Rp#vm0P)u=ZxmzUn`-a|aX(&w7E_*|T^^aaPb3L&OK+ zEUu0|tW4_LKr|~!x43Q~9GCsT&6qOq{R(2}LL${AvA!%pf)WeplDNq5Xc5mOUpHEl z43G9Tz{k%uRUKS5JDP)>!om@ZhTpSz@M_!RT{q)%oNFOIN4sN_ZQCAWXWI5e*8;>X zRgebeI}!YgwOsPq(?%|&k(e=2@$}0wb&_b{_5wgPFXoyTQO&M-^Qq!~RPoKYy)vru z$;GQYb>%3a&>sjwId?3c%7;~>rraV0Hqev{&8CpDk45m48l!D6)>D?^-L{5!ak4M3 zgGP3wV)Rqe=dp30h7=nV!7mg8qf!h_6&G32kQ*~^ai7#q#qnguES5b7W`vEUkGdj! z6iWcU><7|1N*{GacdqB7uIP*E`KT*8tDf(93?IGa+L=~VB!>Ma)||p0**Bx3#bYI* zh?P&N9Ua}4Ds)$_T^#OVnmw;d77Z3IGmm==tsFz$Hj%igt&222Bey8m3%O%!YpI{P zR7PDlQ7o&{8C`YKMVW~af@#9vtMf-yJ{#ew9HCP*bydy7pl^gI(lN6kPN5@pqS>Tr z|CZg>+C_h@^fsMa=t(9R07Wp=N7WgDnAbPVj3LI369_rwhq&4UbyTB=5~=zdm<{Wp3|O)x|rr6b#Y)l5tYN? zT!QULfVynHIUSX_6dJ@npykA_)QM7!NTxFWCpEi!?uPpZes`2@~U0Egm@Hf?oWQ`crLlOr&L6@Fdj=#S~gvZf+-7M>Au%atG zmB$xFBkX%B&rmu#s&dbwRURHn&(csL)$#kg^7<+5KNVUNE>!1JSv^o37Uk-WaCH=o z!s^hR(o*DO4ywb+WAei|l#E_1y(Bz8QS5Zb;wMeQDt?0olZ(-0GLEOTaGS<>7hB$p zRBuKXl=4&_Toetx+KtdGBpl#ORNOV_gxDLjoY)yUF+n4e$u|DKXo!EQMzpydgu4Usyf(fD(q;N2yj>qbr$crg-$oosS0h0IQC{*x#)i#RPPx3yArY62` z7sUtSeBVBb_rHAKLAy`Z?o+h;j@o@EO@69|cNX`TvCxT8jcLN=`zyu0Y*7~vWssR! z^Ddq~;TCoA3=g-ci)VDWMQNV87NvRWT9odoYf(2(U5mPF_dT@xo|>Fq8h(Mex4J_y zt2XSjH;OTa&`qw%A-UF_RWEecx-TQG)x~_4r=K3WC6+^*C3Q(`9B*wU*q>6D(3001 zZgH>5Fy;}hj=-Wk5$o-+@9AqUvBc`I!0Mg4z-oJ8YGu{pRkpXbb;ku($4(nt^+~-M zX#xdOic-QD3&O?-$~*f38f2{e0{7s0##rFat7nXb?u+UfW1)LkJ!33%kJgRBbu4sW zO%D{+#kY&y5shW6xB=`|cf>}5`Ibat-Nxq6v`XCUMiF(CM)GmlvIelF4PfPw^32Zg zRIXf1@vjjg*eaJ;ON@@ka*%tY)AT;6SY_~XxRnAcmo1JKeMDvJ_0msax2g*F5(Qkg z;7_hW^?KWQu*ha4$9Wh}PN<{OtnSOVut8gUSlrbJecl zdXI2$*di>*E#2Zuq)@6Z#@>uWjS%CuPs zgck-5-(X>Ir^XYG5wuOcw5L9I@(%sH+tlkD;;q}JmK)i1Q!O3*tuu0IlaPUtb{ zFKMLch96w9BLT+l)N*1sYlINGPeU&M7l^R2DYS1!<%Y_9Kp_t$XkjU$c|@rghzXYp zZ)dPj^?E7t(0N9}0xNcx-F_ZsgwWk?B_(VsHLdUhNta+XB%!QMf~LP6u&kM3P3qc3 zhb9XI?v6d;rq#H(>J~+>^64Sjl-CzkVob<;F(y)|%p~SJ0X|-8OmgvIfxPEK0xmN} zjF}#ENHtYPC@$Zf%hM3zGTmK#ASUnmAdJiRbk!|Adb;?Onq~FU^t!;s=iFjZcgCi1 z`>2w8#_kqg0-do3(1MNYIAiA!AJ=hKKF_3Powd0R#_^IC#{kxMAIm+X1NbD~(BQAI4cZ;~t8h$n(A!kivecs|i zYg!THt~|D=;|%M?h`FTWAFLM>^ibQ8mIW_G@Ni8pb4RP1I?{>JD-mmvj^snHB#PLW3bl^QTYF>7eHS4#JO~S`tIbm&sKsqw;+tcO+Pua$#~1aUnx(jK zSjD%~K1a{QmTGwHe4V&XBkD#Tol!UGEn)+Ozi|{#5f*T~im)KhS5+(sM-(cR+dwG` ze?mtbb#X{C;t-Wu+)yb58+D{wKYCHAwGEX*z#rrCLS3B1HeBrchKfgO|edW z>f7ViMx^7vv&WSUl=5>aK0!T?iYpo@mBFR>K$wmZ8?fTm21@n$kEPBvPzt915ADi? zThh#-dk3udANu*421;2`4b&B$U#OqWdKxTYwWBbkE-`+tv3;HU!u@a5a$;v{L^ARF z|EeMWQjHi>nMDlZ5+dz8V`JB4DGaio)rheV3!>dF4HV^;*#3t}m6KXdY>iGF(1>vJ zi-&RZ8!faJV*=iLFFIzN%bP|k9C|$A@}|RM>mKcDlWOasSQw#3VZ6GbO@EeoR|7ab z<@z;@hfh1)v02osyb%l!bUJj-#KX35$B2^=KAeu3SDY+QMe7&3u6`lBSWOkg&Er$D z%yeu+(WJ2#X9Jvcc+H3Hp2})Eaxz8_74zVsh|wT-tc#sfK82}Q zjGjSujVLCbdGxC@H$BVDi(ujN`N;EYl2fQL;fy;{!g4KhoH*gHFX0)DX|z2kQo=4R z^Slzi*O*4zLn0+4_1Eolo>p9-F^#r^krKvfndjBvmikQgrFC-X@x&t$EDO+UqiLyX zI4N=Ntk3C=_1AZ3;{CZkuYGYnskE`_G(fimKL9-M!@{d0cv)9?Jg<$jXy`7q=&VIs z7l#KK9U?A{D7{D<-c6v#qed>-L3=7fEL$63(R_;@_n*>P^guJ3M>VFmI_rM~{x$>a zwcb;?3&+x7&FK|}Km%M0zunQOj*CV{L4B_Je(ls}vhyP4JYSzBQVyNG9*AI)U6IUl z7Q%xOa_BjF{GfU*jYIfYOTt(0wA4!0FRFWO7U z`ysT-?^$Rq8`xmip>a`iV z{FnO|_?@^Mv1;NnApa6lLLQ-1aByjHtK>`;kv-i`mgJV9Q2)0kPOAAAF?a) z2Mfa8O)u{Zi{!B;TJoYy6m~ymqsPY~%vxB97oAHHG}G{WCXCpzmyrbe_G48mRc@HOq|L_}S}ntfdf zA49>|kF}iGw{_xmop@0rl1Y;POAYZK)d(EQ)%S@3u9z0oq|z)ZEUy1%=X`N1%gRE* zuvc){Q&Xw-f&(!Yo|iGx7+h174j9kk3g1|EuH|aEs3lw_%F1m50|xDd_TnZsZt?!K zJ8?kVz_|W#xlQ`T;ay@0L@6LT4IFLp(r-)zD*!L&piHCYnh=;I%F1lwYEd<;1)`{J z=W<2yeTXN;9>{|6Xg8Y5zgf@khTA|B!QvWK=J)mhc9t#X?TYDJc7 zC2_W09o$;j7ONmu7)FQf>P!q*YG-FQ8Dp;-7B#-b#1y;;c10YCYp>eGJt5oFp^5EI zjEj?X6I*heJsxIF>4gko&6IemJUPXt?2eG)C0v)%i46-2rv@$RcgyN5<+AQnt_uTE zT18ziW>-n`*h5lc;!-@hO$MYSz(As&bbP-kirprq5tZ+z>SYT`?YnDACUkAp*iMSc zYWWv7UXKWV%*Bdkz3~Q}=)y*#9SFeOLFj_8pJp_(sohk0rs^Sbu9PT(^}6si8DT5k zCcTYbh`S=iL$dm4y_&)`B&WFhu+A5u&FBg;0{2*7rpbiD1{W(e$Wc3=c1`bBuurA!;!&^`*!;X=S{*Q}8Y^Z~B zB!@J^CEQhV?BE^71QrvdgyIRh2re8g1{YcQ(ulE!!8ua;UbxOYZZ?y-O@_9jR_04c zxEFv?aTrD8TaduZ;$X?kX|&;MW8rS4hE9yTGER)HF<~5oj#2y}FA7tZHIBWMYvX~$ zeLG$UG3LNGLD$N%uFxDMF4DOtQjagOThU0nvJ31#2?|@2dqHE}FQ?RX6te$@Ni+&y z&@eC0t{JOl;)SMFGvA+SkM+DibMH(K)&r|OduQG^3&4G|YG>KgJhihnU1N`xg;8eZ zOuYQGa^|*~_Eny3GdEp>GUHYovcYz4hCSMIj%t{Q8o)gT++4$Gs^P&|03Mw6=q&q6 z&!eaTQbw=VPN6Y>JTS9*rYmE$rn>?{Z~4@Y`W;zvgx~K z*wey2dDXMi(38(j1M}rz-ZjIG*D;k5sis|59lZ*jc$CCl363Sx(dA2oICLV5yGndn z#PU2i?Z7lrylncm8I*3D@$L-!au(;=f;`kwHvRtT3VMB7@pOBtkTF%ORKN8c9JF>Y z8nSlqzQLje`vz|x0$}@)hljfHT9)VGp$CSEoCCvN7>=A5hJQ5NjThBC9}Pb-RpgwQ zT6q;il~+BF8Dn&^{Y{}V?fo8xqAxd-aX~>DWE@}5}L|%XzKG1$B%cB%BJY!M~Bdmesl=>X%a>sjRH9K(^&_GAu!aAd1x#}@yX>= z;I`#c$OB(lj2=5O1+eI@UDikRhi-Z-K3PpaMfHC+hIoMQavu84>J(K@A8Ako*S}X0Qe61@QAJho^w^ z2q`x;tQ?lx^c@7Z7V$|LPwa*$vdEWNm^VlrPwe{u&SljE0MtMs0kCl<2%RQ~hEHJG zEC906hC>0K@o{%BV2=*lU9{fwEJdcV2z|$fBRMv_b_@p$3ORfentB332LXdZAUG)2 z5N*goO)P0#13|QQj0hMcQ{0hIaTgA(B$uJ{2p$i?=B&Vi5k;h6+)2T>3(pi%@c6m~ ziQx#AXU*_XHid^!_A?`3-iL#SfJ_G=Ai918vtY_LRm?rpgZW~|7+ zJ3k_WUb>-Z^J4z14QKp(S>@#o#-~Rvf4t$0$1i{C^2qpPVUeyzxEgSRR;X|}{;cZ* z_8S^)2ZlW}oYH58qn|Iw>`${WX82I{580r9$bK}3Bh{lh&Ik@l&WM*sAftN3_FN>} zbI;~-{@L6wM{@p`BUj`jV@3WaG`D_|fBPtewA)8LJPOpqqn;(|*-$;?hxfkMd#xg$E6wzNe8qA2;lzki$+o#^Rr9QXM9{kh@1kNWJ@T zuXUKv2OX4y(Emfui5ybziA&aA0&@K&UsB?X_+$i<10%Lyie&qx?_5eezg@a~B*@B< z>nZti+#%8JvRnD_jh7`an#S3A^BkR!7)e< zjyW}kvMynTu5PYXP6!zv;J-g_x! zz#;Q*R4pxxeey=bn{`+Y{lD+Y|REBCzjI{8u6ZFhb|Q5^Me8O(GdVaE=2fvtSkY zuf%d6R?6kRHGY_Mjlb3}Vz)AxC^1@<7)>R9NW=j90kXl0?P1*|n8$$mi$sjMFGMY4 zsLZKE^!O>#Cwy=zwV9}7Pb9v>KI}{n`&jnG)p8(tou3!_*CL{ zAAVJ}u7u1WWy$s5NL=B^T)x6z<9Fj_cTWvkZcl=Tcvkwi`n|M6?X>f5F`0X;ava`z z%IzJ|Y*%iu$GQ-9yKqOe*ysZDSp8v_{)AMi$lMSJh60?e_I_bza7`6lQ{_TZ z4FytKh+O8ziPOG79+AB<8kJ6^nF5y* zQf{NFv7#z*HsSfoe%S@9Y;wJgM6}WJ{L}slkN-R`yEciuH(ib9;F|{&CU8{6muJju zn0I!$?U_gO9oe1&w8*nOLvoOtL%E;a2=*%MceK||dr^Ho3Av4d}_;A@~G72A&3 zPh-(lpT-`E6oSmxc{_Wrg!rakw!{iz)v~H83&%9+PN!V!0&&E(Ra4xl2&zZR(Y1RTCJ1! zw|%N@T`tc@b!#K8`kv-5H|I5F^Chj&;Z&Z;)veaH!XvK5o(-+GwSw~7TAgUa^!;tu zw*}w&w*P<{$p1&XAKJk~Xm0ZyX@0aN`4C|=2~t%IqJZS9gC+=Qg5bKRy7iAZF!b4O0`BrH)<4_2YrQ*y-G=?T^$Uw5~z&%6FA{_3~6&#amuqP=!a)h z4y7RXP|7zcq949V**6I8NS83*`97(^%*G#{gPqTICTpJU{7YA8@O#(W)2M&b9#13m z@w8Xd;LcyBeMiZ6X}>f2ds-+Rj6L@9p+@HzbdDTicD$wcU0S z`AfIb?np|z-_xDYd%B+?@=W);dk}edkJ?Ps`eg6tFG8}Z@6vuq*7SR9Ad<%ht{vpY z%Oak&gZ2!9aWK`8u2Gruu(#$m0CBp$)D@%irLMR4z{dCW9*280S)^?-7K-J+Xz}ho z`}=_Rc)!2(m*;oF9G>!?@AO2*XT6L2V3ZX1xvx*FIoeD}LRR17=5G|i`}#cg@3Nok z^E!mcuJCN?y|p)a-PYbO_kpLr+UGSSsMW>_6WYItY(5vg4|S3~?(4I<5Ajna?m3Ie zcjW#!+;yIhyZq9%k$Tr}HI%=%Cl$H3=d(SrB6+swcRdknzw23>$;R8%dw(CkS$^@- zetdJh-_rhJyV4B}5V_LTq&-Q}p6t1%r+YD0&TD#Z?*)2OZ?tY2x6ZSD;P!!}^Y($q z(Otrn^*%k@)80vgZQn^dme2Ld>7Z)UmvjTQq}x5+Kpp6Ih^Rx|&Jb1I z{nPHCcJ=+NFOpxNyw+!)^(n8X0Dc{#z+L&?BF{G|FLwm}a>spenfJsgjpvDrp1&w& z4!;z++H-8c?*kN}Y3X^P%bqSM`BjgTJ&>I1xuh48CB5G0g&B!kdnI+$Hz@}@bIixx z(;eO3tksyw`JQ44P3j;X?nvLNUhR3f!h+SyShd1IaR-EQ$oXS8zI0ZHhrjl#= z9q$(#K38F31i8IwVPR|fZR&^bniN?bS9><~JJJt=j!>zEIQAFv!$$Hv+~H6MAh zs;~{8Xn&x+Cw6*Vn4q$-E#Xn~s*ifr;*q5lH(uKqBGxwkrZH6frt#_|(Yn=1A2kE; zQM0|xgtFmQ@r|dr@h6Qjq(5o=Rb#~CuNps;gz@oE(sNWC`-JC`-bzCDTO?&FOOfM7 z7vp)i@#j?e>ZILCNOmXP(IoCq$L?~AZ!)g-+|lHzCQb1Z3G&-RTFy^RDx0E2Wz#)P z$##2?!u)%hqBhKHV50ZgtYyBqVFOzCGuyPs{n9>ciK;nm*W! z>U*%+gUulB!Dgs$3{5NeeJ%M(stA>SZHk4?uT6`ak#lnqQS|??_a5+7RoUb3IrrRs za|tOo1q?l4fY3q`0t5)x9_X7TO zuLF|H{08P7j&ki%j=eK_Yb+iA=aWz~UJITMIvfl=yYoWmLR2Ud6gc>U7_e8~(A*Jo zC=Gopc5W0^nR!}tVoW>IrqXE#c&>nrG;E)IRDgt ze}BXIW}h4SI`{Yev7d8)|GjX2ygASUypjx=HnC4tJedBcJmg&2uJ8zl3 zce?X~=~D1`sT5QXO`Jz&t}Suide)J%oE>;qjyL4SSA&&Lc7dZH6Z{epjyqhwz&|(` z)YY|icha%L9OQ_M#R6WX{>WbG{3Fw#91Wd}9pRH`bz>G1Jd*cmoqyo-5=IYs8A@2c zxlG}V(Oax(#tPGAwVEQ-3eOOx)tZk-9eCu&qj)^Z5II&V=1B>z?q^yXmnYAcaj)w^(qrRt@?c7o*WSHb|t52MZ5iCC>~{i)GiSYR}Xbf z79lsnyD7wI*PUXbyH$j^HV6-#2?6~cSB!|tgTtvYB2u!cVv6b7S0)Nj%^s1cw1tB< zye1K}h2A913O@{WoQsNM;6x}WiveY^d2k5IqJd3uSxfUph!tA~42pFkbTZUENs0%= zG}vcAOI-meje9WIRUmr9iE!6!(Mt*zkp7RwWLa(#8L;Vb z?rc(st3a7P=~Iz$rU=a!Q%l99HZk-<5xPJOb&mxlPsPN`tl? z5mtB^Mk+)v9*U4pM8sMdqEMM14rW8mCIm(@s5CRy?-LOaM4_&QGOQvXYygXh6QaNC zAJX52Zt3U#7|1;iKu0XkS3&GZ>h=G}@P8k}y}@-?1!MRhGKQ~WG5kd^hG%297yN%W zhC57mKEzq5)nh#bD})prVX9p&Llj~gqgo9qfEgmuf-x1k4&t{BavP*@$QJ9>c>lO8 zV~1a)TlEVxqMY292u?hZhJcFt(Gz6nxh9EH zSFV_QCsc9|n!YasQmXrLu>j7duZQC{aRwZYTQ5er*1&}4!hE;{y9Z`T*CVo@aIzQ) zLZL2q3>^e;DvAPipw~J8_ge5VGwb9ZOkqg}WjIZ+Y^~Fs0VhJOwddEk;{k!}T08@~ zLR`fw&YV671Y&w<9f|0Lpoo(AgIi@unH53jD-C(`9qzZ=Ym#v{L&2s?-C zI?)&RV3J!!gyq=*J$l`0*`u-U0_-B_S6UZ1UEhhpP-)0fXryx^9K%&D*p=8PaxYJ3 zzz~S+5dHRvVK`rexT>IHXRPe^VLN0943qN9WcPwU>ULm1^b(=-TvuR+z%_1|`l05H zx(K5Xb@YIfF<@}M@Z=vB1FRj0T^6X7!8jL%Vq(wtwYCL+rOM5px6F6JERA~-GgQ01f2pj~a?q0h-fa#a)Zn21;dMr^>Y#2IZwl9-JR zAK^a=9dr`%E7ZPajT=`#rt1c5X#>ox%l+WjD4c~|hun8TJBC_Krs!=#&tXnEpnUC) zhIx55Xf4LUb2-5zxKu%y6HV)V!*q9m9?O4NgzpjqTvmw*C!pGU)P>m_1*0K(Hd1#U<<*QPeV_3AhXqnk2V!^gv;G9tl zz{Z_oXlW-|Cy9xPb0Dkx0EO~$Q0p2a zoaOM{k`gR*KZ0EY=iFP-L9RmCQ_JL#G_BAYcfN=k300-Ktk;bb;3QItsTt>o7+mP# z^^f4?Q0+C2e8dP6mJ82TsFS}5IR4O6aIDD#Zd8m8R5 zux7^&B|X#>E?G}G*p3Ia!gW1VTkC$q{RsYtErazsHWJ#TMX-^z?m)0CC2FM`Nci8J z>Qw0}!^na>35)NeVgRny9>r7&OZO>aB&;l>TgAwoSb(KHjOLMdz`=6&)V?t0;FWU` z+610h0rweME)d313AuPS-U1{&3x@%SM4XU9Vdq|lpgabyG+oO?3Sv19mx;-8Z&mMB z#DX=qMqnSFG{tZ!W2&OxWeq$4Ca;$!!x;;P5#~V)wBn`Kb*eZ`YP1%r(siojnkq(< zEGoAqsp51x3%3|ZOJ!A-su}f4S--2eYrV_Je^l@PeeL~!zFrjy z5S`#VM+OT7h$-j+j3w8{Fj<;n%mEBDkp_pO@%~rcP9}>1?&WZwWnFBp7tz-DtNj=` zF;J`l8SBT@hsAKXA{;NadY*tgSXY#Y4v5%iMf6l)-@qLn#Jsg)x9Bw&_c&5+rQ5!8 z$QT&ae;EO=NXGXk>P9*O?}G~^c~jjk!yOW?GD6`77p};Mi2zeJ-sd8Y#w-Q*U)Z4J zGsUP1uyt7yu&KDb!G+IGHJ+eZxb6#e{XsUGnh9mA{|>p{1T}@&B)hH>mt8g%#&+{?p9_nv~r-Xv0zsytT*K`n$q1NvWpFqj&P=QUCoGl^Ma~h~0Fw5JY`;iCBeyD`?3L&EA*6apNY)n2 zF>r184)u`3wF^!_zfN{}MA{8v@_FFa2ycKG8r{0BKtTWmD ztAi07ZcTOj@qx({5LhTYv0^;P_1O(IY?aRfJg#%?dkB&~AmDLLgZpOai1lK;AI{$( zgFFW60}TUWgvSRb2SBLASgi*{99a6>xmlhJ^!SS>tH!1)4h5QbQ<815SEn2GRUqnEsUsu3xx;oM$8y~Knz zF%e5`Vgi(UZ4eVmWC>jy?z$5$q$8@boEG%fr=S;_{ooq+QKtp-{gW3^Y)*Gsk2*|X zq1MwE;G~{vJ>tlh<-cA~<`@<22j+2Ew?YJ=?z3Sno*~ZekbleZ?|2`a-zm<9lXwO! zBo_F0Y)YHx6_lF-*Fp(gy$l)#K8FUPOFZx-=^WUnXNYrZ!~|HU`$a{Ym;?ttELKbv z=~F-o5J=UoN9F-Pr^;)s=%X0nQ6j?+H@VM?$?g_7dpTr32q%|&XN*WR1x`|?t3y2m zJPI{06kfPGac#bsj*sQa#SAD;TQ3h!gDYcPZn%%eo1<`R-D>v<>eK+HssrHJryMZe zcDnb$RUy`WtL5_a1;|vesDo?>OF--H)o{0My$lhE$N6Fi9=r&t+v(Z{dF-z9VVwl` z1agaoI}YnTSZfclZd{#&H~a@+(hhMsMn;0@%_`+#!1RRr;Kl|olGi*WuQ{+fNq7py zs5x*6e%uV&jb`}ibr0@nA?q6#< zMCp2w2VTyGl?8Y?5BLypTfNB37C9G*yooqMV!->o;KC{3NYmw*5DjpdoTLWL7I{;k z1kcUWToGc(MKw@J5^3RLh|4_`E~$qyco9<{c;0G;p11nL9jLVu?qwE&KmG)v&_|52 zdcquMHDy57gczMB`a!JDf*A?iC3D6oEBu7$hc7&Yx-#K8dT-D(74C*zmbXkk^-A3; zMvM`uQ^g1$(13i_f{&VTeT5ISFcfi*PsDMLH%bFs7ek9Jn8gNIwa|EUGaT)J$6_!d z@~oD9;PrE14I#t)TIF_iGeEKJ-}C=+w`1OwZjZ7~J1nN(4Q;npu9vNcBUoo)k{To$_+sW zK^}m6F^tOsxY5IzKRO?Z5J75OL)hT!36Q62F)+m7yF_n%xHl*>Lk#u-D#JTDd|KJt zYVN41+1DX5AmNKCkBW?3?Cyu{2DfR0btf+bl^4{A#eQ+lRI&JVkl!h1niPBpQA>Sp zg|3JB(>e`C?i8y5GP&+uZG47^T_D0&h}d*6eH$!xKr5uMo;y?u!{;S96@|m3Qv~~Q zunfT$wqTj>vUmoqk#wHsxA};4^F=zHyiK#;jrIyc+G1*5E>Tn$_RKg6V6O86X3#;{}36oMJ5g| z>GWhcld)1Hm%t^L!>H*cdXMVJIAHCBTL_QUy2It(;hrWtqCY&D5yKV%tvlgz*J!#> z#N^^LFzm^`Q$_y;AUqo%t45n(d75>)DMrGwH3Ob$=7}^fzLa9ES}*%3?QAin1j_EI zatx-e6r-|5+6f%Ywe%0plw z3-eVp47(^``W`fR25i;&q!HbaS(ZW_3 zl;^?16^s>y86xGddp+cqJr~sM$T$E&^^5@raIN(Kte9lL%*KEjHWyy!fyXVl=o;qg zvm0c=AXu=kmw}tIQ1q`AnN?!?QISbui?vN}5h>F^@MAb0V9myL$kM~UDe&~fYKKK@ zPpk0&9L8k8QBxVNpk9ZiQJQu1{4y{E$}a1OL%yOChL5@7VUCF}b%f&Jha*@d6~i80 zX6P{-_V5Zv#4ynVhG~kDQ)lr}=QptO!e^ts#fVZ72G2dplkg}aAJ=8_46e{rl8Ar_ zty(zc+H;8rk^5fqnTI(E^d@w;Pb4K_Z&&2Q0K>IiD0E~9Jf4vWtXw7Cz?tx32%gG! z;q&Vxh`!KPOqe}f);zdTv_|11Xw|vnfnipF7Q(_Eo*{;96ytICn3w>&P}k$~bP_~< z)H*0m1zq@5uxA-$SiC*OXR7#We}r(46CyyyR?n+q7bQfSj~EOJgh?(cDN6?li(#PeE8woBl_Uplm)zz-5|z-cz8Y>^l^P9 z!e@v+r6PPz86<>Kd<_Hq(_{T%*N$?SLGS@XI1B_>H^G(k3IL37oI65cjU(@V@pXXE z4KU2%$^CgQxC$RdO!-zsPZsgeF?b7KAmU-M>e>xoc!Cey9OGg|p*(WWS7@4u$8xnQ z70}uFih?#NY^vgVM+Cg3BRjLSq9iY`qR=~MUXJ0bT2WEe+*DIvTTxTrR9(?nxpZlL zQ*EoE#b#yC$)4*iFnqqH6|KG&qqU)OnXjR~)o+yeD_i{D>S|#1t9%uH!<$uHXEfnY z9K!F;`&XP7@CAHEMa2@(i=@?G=?}CTCC%B@K7VC>Lu)5&Xl`B-SZW}J3u+quviiB% zvnsMOONx}0ImL6cjQS@3xKyYL&8}~%_k&thjjyT#ENp3REGhJ()PyupwA5eM;;XE# zKy%@u?97VnxpON@%ZsxsO0tUb=HwJ1D4L6BN=m(@*%dR3ib}Ol{Js@_zY&mC0;{NP zX$~w^Rijo_UC>Z(0cr>qv$Kn3*}r0Opr*msRO_!ZTAK@+Yo)``eDE}Q-dEAu>@%tY zEiJw#f2*&y5q44uc2P<8TxdlT*a5rhN`Gaip>vAnWE;NLdeDvyZSgHL{Juu$It0)K zm94GywM_>4+}C+k@!af+qT-;D;N0>#ncmD&qb1PP1l?{FEneyOsm@357Zha9DYZEg zeBo}$?!?Z z`lg^Ovr95d<`o+am92jK;Z;3)3TKuT&n+q~>Z~8NLTN>Xih0>}6|MF4MsL7)7$HU3bueZiq`>9==B3RI&9y6o@)+SVaA6cQG&j}8 zgY~k`nJ|Q~Zj4`W1cYFwH+WTX(cIFk?2@4EI2PuXK?|@?D_WYNM;8a`8~pW66^*{e zR-aPog<-ior~@OaBrChXTQ2K?3j9WKoy-)5cX4xzKNI6Xk6z3?ILcIW3_q?$0i zS`COg=~nDM%0AGXyqp|E4t|;TP+3m#g1M0OYMXr3@l|z|Ez;Q-A3E+9H#axQziGQO&e z=74``z~724UD;Yy+0XzNL2N;zi|balsxA)ttpI}wVy_k~Xz^F#BvI5{Q-eVhl)+Fb z%E>9oE;XbNacp58%fsmeM;d%Rq883Bo|lcO2uDw4mA@WGbeF+eTHok1nkwOs8muW3 zz_4XBN^4+v%LZYv8eY5}(;{92tuiv3vojm3Gv&Ynr$ZX`uasS_YllppHP@I4sZj|* z$_Bamrl8)=@njUYG*<_zRGj0uS>7ybo(TbiS*iuIY%o)|l8HLy_9*-pacTBQaOm0 z0#(o;zYoP#1a>Y9loC1T`i;D%YTpXkJebjaRelvltETj)A#mS!F}Y1S7ktIyjf&+F&_GX46t| zu)n6Fs&0u|pDcw?p6gpy-&)_?GzYt)!e)~m0do1%>9CfT3R#cpX5BZ%Gz>8)aB|Me zqEMTV2B8+rLPf=}Tq?~gngeSlnR|o7Rwdzz&dIr?5ZobyUF%enlgd5V;#BDXJ+pNg zJH^@AviD0{Dj_fUp?NrRRea*5DRSm2DXeLzw5{e!F`r{FW7Da#aUB&5sjSS>yu$39 zyn@mo%A0#ep|@lhcGJRC)!w ze{S}C)xTXP*jgVfH07dG&C@cLbsCYABf1w&SPGM&Q4|~$(r`4(sI(7F9T+90bMxlR zQcDu;PpP}QIk30^a|KOU1(~ICt&WkT@>(mbz;JO%%LjQi=BvD{Ine6PD>F08GYhhl zIY?2ne}mjL7y+Cqt5b}chGwu8YS3$Wv_^J8aaLZHzXc^}y27}}vsH$-09yzlfQx{x z^B^p=rRLON{V?r&OAHvlD!6R{+ZhuG)uf)<2rDmGZEEjf45+x#uF08O2CGXWTMlis zCs~dVJxAaLGCMV;*;a-7K3ILOMx zb$@*`+*Cor$Ie6{*yGJkk?ag;RA;_}giUksd|y*_b4y;eieQSNRXB7OV@fWVyTHgR z(n%bZ!tjOA#`S)%e@Y5d@XkR;DCCuryoK4g0xH2d->9yl%D{$Jc}Lf|?9VRDo(ngY zndLH@v@Z2k8BH>rb!Qc4XBK5;2kQdYV$!N^uB!7@Em7XrldxO^tA4>g#O1SJ&al{= zoX!B1CS*uOH1@D8SdJxGVFt1#Zn3=(~YuAjgzrNd8%`IP-AFm9^N`&+tj2vYtFp*%#?}o zDJe;5Ny+hJ8vTJL-&9EbFrccSSlLolH+4n&gfSD+;>Xm+k10xxA5&ADmkEC>TURz! z)wMJ?K@^V(G%bf`1!Me`kkVS=B)F<-$rv0=u()V$8Pn0`IkD!Fre(J9`W!ol@`I(qx%}tDBoT2e6vzJ55vNPV-c`(?nGsr|xy+ zjBT{mK~j@@iy=%+k;1^rIGevqcZ|;48f|={C@>1CQ=caR6)0 zFpI$bkuJe7l%rDxZI|T z7Urpn329>*>YD;9#?&?i#>xaMMH=cCkFBk$`n^a>ioX26&@~o2s`cNRjSqGHt$;Qb z7eW74u(`Ev`B>Q&9D6~Z*^gCsRn$NlfB^7SC&80ye|3FxDjY~t6@V#E9<;#I4Y?4( zWgCjf+nq73@EFVob2qlFQ%+_pDAHoT^K3N?fidu}Ny^F+h8PyH@vs9EQ5=ro{?~^(X@;7gx68NjL&0r~`PC00$ZJDJ3X}@0OpHnK?N=ao%EhW*VU7$(WRYET*hZ zO-)KpOBy$VPQXWu;d7-P_=g`xg*6R+xOhJ@{V{&|DOV_)tKiEeR(Q;QVIl4g_|$6w zF!*L8R3~3;r7C}QA>Rdq)+|;E}Xl}k=s>Y4(juwzAV(Y$kDyNT+sC>>65JM-(qPrLrtA4t2TZCw&b={%(TQl@qkBFxb{Cw5=fGNSveLl-g$YL|vt%Oa6rH zsqB(|le)4Z7FmO=Q|ij;iQitmi*#LqZOxAGT;FMb0YSdjs>UGpDX>hO@AU9_fl6O_C!U4AP00p0M?-SG5o_$=Vj7*~;KUFUE7lyxIt(G73vhOYsR`gf6j{TobMyOF=T z8-4@nSxe5)zg2s8H}a2k!~fb1f2kY(UN`)!ZupPg@DRu&r|QR;ZusDC_^599#BR8^ z8(!QEKLj4(#^=aEpL9WP#Ss+cev9J*mF6r7|fQ%m`N9_01tk1)1usd?6IY zV3CiHoPYP|q+(V<(M)eag?gc{qSQMRZmgwZd`>A30 z)e$}sz$5vd6+A$z)GhSyUwzhs>XI%u+S0G`rVNF^X4|b5l;n$glFJ)Nr3mY0j|J&s zdYFhMGQR7BfEJ(q#b6YZub1KhSw#=ok#x3*dW2M}CxRHnhVgfZZ`^PVjkDCRC)IYH z{8E?S^Dvs9x%_OJpOHt~^jz5uf6c~YZ1Ul>G#Lr3myNF^j{1As_!Tye-|?yKpCo^& zT-lFvoaS?F9DWxo$Xki?dM~6U0B`RwS^^-)UxU;7#}Maw7TM(Oe(vaoKWXFrZ2FJe zxZQ7qX&ys+2H51QY#dXkwsX0S53=$1ZG5neyJ(U?{dRtOH+-s%53%Vv&&KWc-(ll; zpQi1++s5N|IIHg1;>*tlK(Q5(0*e{SP;`9+x2V8ePxz(3tD zZQbxMyWtu1MaQVeo+mHDWCt5^3{|cFavR5-rFjAU#3jm)vhk*F_*NSqZIi#j#>d$B zqc)yo;{}-PV8eRte%ol{cE5dR<97Ms^soo@jD>%}b`i&E4^xZg_!FkMp?os@)BGYE zx987Wy5aB*baF#IcKLU@;oo<|6ETUy#`WZP!{4#-6u3^?KabYSs0W9d=2OvGupzhi z`*s_*`>h;16*eyakd52r$6|rx?e#ux<7seS+Y^UQhYj_2(PQKGyfoX!$HO&R&mtSQ zkE4rh+^+xLZuqMNI+sFH& zRB4X1CkhU=e=fFh9DUvR>$%y+?RH)k*{R3A zK5;87f1Dwdj&Y}-)F8tpQi%ttT8rWyIjY392ySQtZknr-TFc=v+%D@|NqK6d{Hx@3 zU5Lp2QV-VqFg?;qx5*7UE^IC{=@4&xs3Lj&Ul z(F&Z$_z_z7U%>b(vcH4z0kr{!>ND2Wc&dt<8v$AwEjtW0GmbjYVN`00ox$PH_`YS!}x59Tirie&pl-4 zG$#L;Qg7rlzJbQYe8%UI9~Lv7LG9Jgk#xPYXg#`$$!{QgHZdMf@wSC=AJuy`X7@t7n;yK30Qa`=U_($Zo4;cT9^1weC&!PVMp7EEczkXr-BZ`M% zq)+?vF|ubg0I`8%n- zb&TWM7q=$HFQI<)GyX2c+bYJlk)4+?uCyD*<&4iD`R$BPCp&jAeiQlaX2u7T|Nq4J zXB2OH884zZe}M7t$eu?Te}nWq$+(ZouQC1(mEUGOitIeX_#7&K!uY5V<+(2y-$n6! zg7MeMZ$B}97R_IB;RY&ooMcd(gpggDUqjemF~+~9etDYl5|Tg2_zd#TtBkjkp0^k`Y5X2xJc{_Ij1MOM72_$yzhk_G z#@(-s->SmduxLE#@wJxXGnDapbbS=#2~>__{88%1L5%mNaWsta0FB=N#kxN-KSs-$(8BGCrH+ix|Iv^4QsopQL_oVSF3;=R(F`q&#^A1&Mti{_&b7%w3`BPot_y&cpa(;0t)@ z+Rr*3Vi?!)FqrXtiqEl(UrGHkmGQGF4t2cidQFOxawfllcrD{IDX#_?f0^=L8{_lI zouOXJ~d#+Q)&|6=@V8b>`T z&uM=?Mtb5HpGJ9ODC66Sk7N8@YVUN$z2vt7#`9>tC};d$%1bqje@SIOp4n#+L^qM z+IcnO`uWivj5m-!A7=a`vgbL*>u6lR#rSPBu8%RkgY^Hz_-PameQA8_cJ(KJj%56= z+5e~{2Z!xFXJ0%{65C` zRBG?@j91Y*{0+vhCjTF0d?Lg&ZeKJ0CfV~Z#=oLG)|28+_lrsO#xXvC{5F*Fbh0Ov z@fF0UGkyuhM*-t6Q@!Pk>*u63jDJn-YGwRaDz9aHImPF8#-AiTw=mvFcHYA{eEE{x z{=)c2l&@c4d_Vb3zn@e4c?9+QCrmz_`so|Smrx#%$qOvddQwPUKgZR49IdPS(mba5 zBGNmY@o9k|@mHx`XEI(v>xVkV&n0^TjGrL?uV?&c(sLQ(XV7}= zI>sX@&hKEn7x4!e???P8#`XK64l({6Iu^F~7~ewv{DSf4sl7ilej~NZP4(;kx`oC? zZ^oaYyqduHII1_5@uk#{GZ^1O_0D0ufa2;b#=j)H&t-fE)w_c6wbDC=aUtX9li#*6 zK9Ka^$ao>i?`8aT^3UUp51{tG!1#^C-)4L=^~-0BpGSWFp7Bj&kCWyN-Cq+ZZlf9B zO5-J-@qRR3k{CZge#l@vlk$L`uXMd5DgTr)`4I9?4deR0%g=ZcwR1h=*<@!2<9o?J zI~YGe`F}U#k>uxnjDJq@2N=JU+IyJs_b5J(GQNxae}eJ*sNeNRD74>}QG7;_pEQ4m z>{-kB^OVmoVf<6F=NiVBke=Ha*YoKEjO+RIX~y+D_$uRi9z4Rh&f8xxuJh8bjO+0j zO8G$hL665c#y3zu4r9EE;yjIUJulB>{7s6VV#edhzH=CVgvLb!<9a?@#klU@O^n|` z>&dGa-%RmvGvh<3z4~=s?VoCr|A5I)pmp_^jK48KS<>^#`SZ>t&BUVzjiWSL3ZBB_z8-iM;QMH zjqB$ak0w2TXM7Cx*D=O#CH>zs{s-z8C&i=ozrG)gW4xI1WCG*MC{Ly^evIm!&iEh6 zKZT4JP`)~o@wvq7880KgobgA=Zx=FtC)sm3<6n|LcQUT+e}wV#Xr1{S@e0yk z%D5hKkj4l7m&Z6W!yo2ex31HicdXnX#Ezg zpHDJ*{rX%uo!9bu9p8`fGiV$wWZX^V8pc;q|N0r%um7!Q{95vV2jjO;yY?`y?_(cf z{5OjK4;k0@gI_cLCFPUf7{7q}Ybd>6TKne}%Bxw7H&NawW?Ziu{>b=~s1UZZ=>wKp z|6|ne=Q7@l{J)%W{rcGk#&zDiobh+5y>~HQN8|kw;yBGw$VfY%X7V#=9z4eQ3p9S6 z^uBT3t{*9G(;0t<=Ih0b8>HtF#)~Pg?q*!SAL1Rx-=jwS!uZQnZxp@HT-#Yd<97<< zsnjp?8J|IVHZuMsjq5)#ek;YvYmB#0JbcUenG~0?^gecNXD{MujGs^X%NajJ<6<@A z`t|G`jDJJ%@G#@|$!LJj*E9Yg#Z@%DUtQZdoAgX%{6`wsHH^1V|6a-XU#Z@`jHl2% zdx&xUy2abXFCb6?&E)ks`qjp<3n{$Sho+#6?LtoBX~55{$xXLck2{ZzqkMr9 zGh!LfA>PNvu}L#fA#4dYj(S=ph0i`P?jt_N#!-)c&Y5oGsOLeF&tUw|#0wbzns_ne z9}}Nv<5+Ki9JI*BvEJR3H>w%GnRva8qaOX-)o=tpznSrmh(F8tN#Y+cK5(3}Cko#L z51Y1U0`aMg-%i|5T*qw>%FnBry#Czul}uibmt8iF{c@cAe>3Cy`#A2faqM3`UhcDT z)YF^hyN4L}5P!_ZQI8%k2W%Ymq?7y$j3*I)#l}(3RLZOG+BoX*k^B+H_3LQI8Q1T7 z`IhkuNY76;j`c2tf4GTwxeGoA*75JLakNK&XGkpL9i+dHjidfMC_j%RuH)p%v2YT$ zig>vT#!vrbRo=w-IN}d6UPSyY#;b{2gOxsA?`Gnuj6X9gQdR`boT#t(fDUXd~^7?xQ@|nCI7xQc!`|Dwcs;`{!n~0xdK{h){_TwG_m$qw_*o=>KjX#3A7^|Q@h2JA@4tH1#_e_*Z@CNVya z_)Nw>B%Z_gA>u_gj($EYTVoh!**N++nbx0+7*8Tz&-i5Gjf|%gZy~PZ^W_n$;y*J! zZlvNLFrG{K*Gy37wVty_t8yR4A0yGuNl{$NBWI%{rQ$2!_{@V-fg6(7vn3UmE2&)U!r(UVmy|fS4?L735u&+ z#&?jO1&r%{sbXCBOEcrTU;eX^d?#_84|Kb9{}wR$*GRsY@#l%pvvEYae;3&} z+GEl4sA|T4AwBgrj%Wep)d1r<-qzYU>K{$|H!wboc)N|Ge*Iixn~kHM)g-UK-vIrC zT)*$(dYe4zxr^+)-NsRVJL%cY_*KO3CLWA8H(UhUcQ$#{e`l1$#xG1>QTW_Ct#@=k z>i8UB<7m&Hsouei-$s0xjbpt!K2vNQ_3S74@r*x8e6o$B9vz?AHja8;CHXwYUm`xo z#!-)s&vF|_J)e;L*^D0{zSzc5kB-kq8%I4SN&Y;>e<8li#!-*XZ|iIv_4KCuwT+DH z&(~jUc{vmP4NDzWek0bql@^BkRJ-?8A6yx6!?``8~kIq+vZ5;IsqW&7ncpu^;Z5;LJ zd^O(2QO{J8pTzh?;?s!hJfqh$*V^P!e=)tTaRZarpF@6#$?H7xl#Qc3Zd|@Tq>ZDV>qw6^ z3JS1=!aw9!5qH}->d|>7#>P>8AIYD__Bc5#Is7L3S zDK?IJekS?pjGrK$W#gzv=b0iKM?HOMeOtn~eqT|UjiVl&XR2%*^^71rwTz!myurp% zkIpm8Z5;LJ_Z$6z@foCNGvoSwLYFb#LGs&d9P8D2W~Yt!1b#Qk-^BQx#BaB8)T8sv zy*7?|o+0@M8Q)L*Q5#1+I?p_7pZi|1sB2Q9j*2_UQj>&!)+FEEq??BU^|D&>pW9$<7m%vvZs;pX5uY2j&p zJA2b}HJxu)l6+sr1H=ar*M7Kw{4ksGKN4?Z{9fXl7@vwp!S*oY^NIhR@nLb{ ztgvyk=W)_g#rQtr4UE4|d@19v5D(b6z24O}j`e;<^6MEtO8gSyx?LBCs|GyDcy)y0 z?y+jG<+l+pU|jpBn(>h&e*xp)65mW*=fiS}w`&;J`Q%o{b^f`R@!^zj_3LFD!??Dmg>n5ptJREaJ1=Hj+j$k^`t{pNatgvy^qdz~j-o{anem~`fj2Dys%{GpD?xyi_4dbaazHYX0)PEo8xt;O5 zh~H`Bs9%51^FbR&J%1zlM;Jdye7}vO9{stG_OtAIX2g_?g6i zVZ4L*NyaZB?xYD<$NzQo+#|xq(ar}*KAQ1e#0N7TMsE}w%D9tw8sjO%(-|K_e5#G3 zKcA%M8?$U2?VL~Yvl%ZSUP@f&f#V*y2)1*&;j5UQ^GVNY##a&7-wVq1@3zU~Jl-N} zh5KL|NBb`%J&!Z4-_QCi|CsnO#y=u{g1C;8Gl>6clSh00NPcinPzQScI!^LojDJPE7vl!KkGT)y zKa>1O;@X~MdOvHLO&;yJk?hH`arDnf(x1zCBJndApGtfl<5P&A!+0U_#f;A)UQ1lt zUr+X5W#g0zWeaYwaR-n)DZf3;`2ECRVSH?ia>Q?p&m*2XQJvTJ_=z_%-cJ0FjO*t- zFEOq^*Cf)r>i-w%Nn~8#j~6mt8LO^8mvR01pUW73nB;deeuDTbjQ8oK^nb;;{=8Dm zq^|a_B>7~<_Y66_#;+p2 zmhs}gO8zFsTZliy_?5)JXZ#l8{ik%bXFu_oj2F}U>}wfsCw?*GKM~)<_$V4jM;PBs zJUpYTJ$DeF%=pv9YZ-r^_%_CWCjJEDqv?IsUok$1c)zJ#?RlSgHsj^=KC2SqdR@1{ zQrA~AuJ3o2GOq7mwlY4B=HDHRYdw!L-UkB(C$xu1F>S6O-5at!e7K zo-bBXe1WX~iful;m=X27tn(<@AFJk;>;+q-&k@z;odr;ok&bUSVdg9vtp=AFv-S9&;J=m_r)UNkz z9NRU5^nb|saN?gcK7sf@8BZhrBjaA;zcN0ZxMPMgOt-6++BJ;vKM>Dk{0idd5Z7_| zDe*?e_2=+cGX5)G2;0UPawlIO#1!x&&1;u4{sztOH!!aC-^uuwG%g-yT@@%g0x zTgG=8YIAwjUi(e|p5|cUI?pU1KM!TRka!Z~ml028d>!#AjO)+8O=tXRlFwy)YCmOn z0pp{H&tv>H;^mBAOMD4&6V&3m@gU{rfK49bAOGRD(Z(_U-z7cmjK4|za>n(%d==yR z_i1)A9!K-$O^oZ$*X?FJmE`v_o<#h98@IRXaT~{WWs>}pjC+Y6WW0#@%Z$$^{x)$F zbfcf+XuKS?$)i2AOf$ZG_fIMa0dSAOxF^+h*c|@kZhijGs?Dn(-CHSD%q9=nwB$vU4M0=!WaO#pSP~`v%TaXud*jzhC=^#s|8k2mG7hg+H+Uvm35|lv~TguSd!)jQWRj zdJHP#0rl&RmL0**H)%eE4p0xeat@V;F}{?_BN^|YauVbBQaP3Jcd1Obo6=6KSD>R{ zo2+(0K8!dmuYz2EPkSztUq+Udi}9#A_JG-?PPS ziP{D2f04NMtF}jfZ()GRpCI{FjGGPx)-WDPd;{YHiMKO8l=!8Lk0pKuS9==Mle+ar`}7-0oJp-^HzqCMI+Xit77_c#=m(DkRZ<)?B~8Yd@3M?|%*> ze`|TYPQc$+#!d4h*m1Dw&+lrk*X{e6yteZT#`Wt{+D}@K{+!)*1h8qYKbLxx^Asg# zB(<(=^j9v1y}w26>-15Juc0zY{+eu3b)~=3NLt+5Y9zHZV*$lel2V|ELe;*-fm-+h z+L~sAjw=^0Zt*Saa=M|pssVo06;*UzQ`u74ROILAI6|HLdLvDV=z8=8+?7{KGk~3{{#%x*>u;vdW2n;aWvjcnKJR>W#ica=c6WUR zoH&*J#l_01H|avOALnUq|4Lw|vi}WAxXEw!oH$keiDxKDeLdEX>r7t%CSY7A zAS2{z{0pD=U?*A8!2Hh{Uf1s${0r~v8m#vEFPFt;p343*vi}g}J+vR!lidD0fT2#^ zPB@fX_@ipCe>aqH&{l27eQ@Gb*LS?CuBfN_ySx7BZm$37HFbqvFLihQi*&s{ulK`W z*GFCV+HOt%4hQ(sE;G|er0e6gAnmpPH2w|_P$$1Wm#$A_iG^Q(9Iic8|0R|xOWkBJ z_Fpgfr?1y}=~Q)tn6Z=wj{e>ml#e>a^*E-Oi9ifvFFFRcCt1VD9CZ@@qRvxYpFB@V zo(xK=1Ae`)s-n6osd;QXZOQro_Uc&uosgC$&!nYH95+E8Eec(eU6A82J2jfdv|xPN zt99!|7Qh+u#0>bpmeY*L2fxp)|NamthK5#}}xxa0{`BY8hpbb*X+Mlh!xRm{Pde}qgr|+L|2Ku*s;8^>U*=K*- z5eG^~4l*7>6Tg4L;hzAdEW>C&r1rsUKGPC`_u3E0Z=A!rv!BGOK>i_g@b^!+1N(n? zWlL<2_Cugr>P7xkO_H`pw&xkuo(&;`p*^>|1Cwue_y@Kf`OPcqp!ROsFcD8@weLqg zw>tv~-uC_8wj-why|yF&%1+t;`C;H~M}F|ix<5Z4?Pxpli&xtG`9re&P2|9zy$ie# z<7d3R^S$kd_pO6)^3Ka{|Essu+y27C*zS2Hx$VE?wtbOaox9mNG!aN{dsaeX%Kpd) zpDEaKenQMMSqU+cxpV9Hqw$69KSn;dJ0#;EcsmCbFa?*;bboKU~c4|XLGm2x~0b4_R@s7PK|N0u6-R)jZ`URI#0(wj6-|h zE;yalUY!ujWuT?10q8enQ+-2%8=a7Aex9}I-dx#9f6kJ-n`XccjZUo29i?0uh|Fz! zI{nPEy=QsP_MYRdcuFeK@j(4EKd=2?-g(~k*H!nFX1BkfOq^HJeo)pA-IJRIy%L5U zaynSmrLWRuUx9y5m)+!{Zu(sxeJT6s^pMW>4{86Zp#6s~eKbV16T2f>oppnY)FraJ zu#egoI~@P+zi_qTu5X-6Vjf%V9qlJO_CqZJ9#3MJdwT?OEcxc0LW5~Li~SO9d^ zt(Jehn=eUt2t;zntj%mphpsoCzh+9Vc?iYgHa-`5=|V_CDbHo6Jm-}^l~~yRsrPXl z_n&8VL_T;ZxBgXZvv=*IrwxKi9m|$`+n>m-e}~{8gl6Q1Rj|tRw*A%2j(qUD>?sdo zg^`#2hK5zWms|f7N*9*$#fX`;MyT@ShI0| zA^U1 zgVp=uWE`FJwr@zlmSF68r1LT6JUHSh<>ZcnYbm`Iw4dA;4;Nxbqz9!TOTh_yG&?~? zo|cuv9}uyhcXw7k8Y<_asy$&K6l@~eiEdV+1qo>>`@NA5Y)int@wQ)*kOuqjpEUiw zy_;2oAa#@)VCD=p%{}S%dvYTmKMuL|nXI%@tG(d98Q(wY@DIwZ{{|fj=MKnoVWp;b z^L-Q1takq;Gul3MqRc^(IV5FbOC1oRk!VkQ!L}JGuV$F5CvHAJVaBBM6S5+&d$R3A zGsEE@y7~NVNOCU_`=HB>3}+yEa~7bigo4OR@H;UvE-2C>A1H_|g-Co|g&aoNrcFcP zZ{)qttDyV5znuT_>R&-yK=hRY^XIkuWKacG=2bnJ*Zve`NjP~)h>S!Cz`%=H?Z=MA z{C4uBydJKj@d1&y`!p4Ln=tf_?E;yO!gh$D2R0<2-rUWl31yHSV&xPXxe;HthTLCg z%Mcypo2>T35Cflm^~1@N1?Eqi)+NB@YxmJeONZu1K5!_*5xF574==h5TCib%NaP32~W(?E# zWdRt%=*g`1@4>|Of`rkVN+H%;p$16wqp_CDq*a(p(5j_SlJj)d=3Qu#^4kM2EKqiu zl*LDlC>w>cDp_QYx>;JJ)=ydOM}BxSd(zWckwr(cNBj*2eA_SP>U3(Y>SeSFR)3Kj z-j}Tmpw3q#H$;QZQJYlFM*H(Wyd9bQO7@5&a#XZ`1?#86`sdJP(3&ScgW!~jEz8`G zBiH@PkLICFYeQr@=-!tY96~UD1|b5Eq{%S^K>_*m*e^d*T)_G(!;0K^!M{$PBpX#v zWJAB;;QB1{JJ}-}n>r=14{#7b9~?U$FvfGrbEt+1p#l_IAcfY#U@#y}eGLr&8$Np& zG#_h`hRiVJO7z$#@YC+fkC6|oM>na6QbDxw5eQUU*q{l=o(6_BmqHb~^RU`unE=+F zpWuOWb$G6`v*c-%hc$KN#w*}p)F#O`!J%HyP(9dG%#+xPtx#2PJ#MoD&mYTxleRh} zI}U8rCTZrFt|mzK3SI(z7Z33!&v;Z}NDuT`tSX!?3+k%_MM7*Q zR1;^UB)T$t9EZapSW|X?eC0$Q=D34&Rv5a&2sr|O&=@!RZMZVtm208BcE^UqMdyQn zY+Fd5nEK#x->0@_X%-O-?KC$=+Oqfb;O z7$Ze{^!3C9kNZUp3Lf{58WlVqV3QvhT@>VlqR$K-$45;J9uJPLh2tovtTM{oS##*L zNWA+G`4gUogE;iZm% z($sE-+8GXYxg~dQwF^_baJ7q2JCEAUR4p)syui_A=Ts%P+8Hh>;!(SJwM$kzLpi{$ zb{@5hQ#+w{;c6G5c9Cjl^pHB8YUftFFtzijU7Xs*t6ienjaIv4wM$dGwQ9Fc?K;$M zm)gO7iR^#}%_C5wtpiMXY{|dsi}{5-cFDi??&%>(sQlYgy+#xIl&U!bQF6Q41A>2&tT9sZ_CG}P3p|6;s zXj5->?r)|Cof0=5o6w(H_MwR;OqkKLcMtsi4UmS1y?x{{75YV#!U>gSdJYJ!hk{Bh zJqLw1K_O0EGgykntHSA0EKwDP%0jX#4Db0i9HblJnAGIpk=iq%6sd<$_*76UGZ