/** * @file gamepad_interface.hpp * @brief gamepad * @author DeepRobotics * @version 1.0 * @date 2026-01-07 * * @copyright Copyright (c) 2025 DeepRobotics * */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common_types.h" #include "custom_types.h" #include "heartbeat.hpp" #include "json.hpp" #include "user_command_interface.h" using json = nlohmann::json; class GamepadInterface : public UserCommandInterface { public: GamepadInterface(RobotName robot_name) : UserCommandInterface(robot_name) { mHeartbeat_ = std::make_unique(); mHandleMap_[100][100] = std::bind(&GamepadInterface::Handle_Heartbeat, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); mHandleMap_[2][21] = std::bind(&GamepadInterface::Handle_Steer, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); mHandleMap_[1101][12] = std::bind(&GamepadInterface::Handle_Key, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); } ~GamepadInterface() {} void Start() override { std::cout << "\n╔════════════════════════════════════════════════╗\n" << "║ GAMEPAD TELEOP ║\n" << "╚════════════════════════════════════════════════╝\n" << " Movement: Left joystick\n" << " Rotation: Right joystick\n" << " Mode: R2 (damping) L1 (stand) L2 (control)\n" << "\n"; } void Stop() override {} UserCommand* GetUserCommand() override { return usr_cmd_; } int ParseHeader(const char* buffer, int len) { const int headerSize = 16; if (len < headerSize) return -1; if ((uint8_t)buffer[0] == 0xeb && (uint8_t)buffer[2] == 0xeb && (uint8_t)buffer[3] == 0x90) { if ((uint8_t)buffer[1] != 0x90 && (uint8_t)buffer[1] != 0x91) { std::cout<<"Header format error"<isConntected_ = true; mHeartbeat_->lastHeartbeatTime_ = std::chrono::steady_clock::now(); json document; json items; ret = ParseJson(dataInfo.buffer, dataInfo.len, document, type, command, items); if (!ret) { std::cout<<"JSON parse failed"<> 8) & 0xFF); out[6] = (uint8_t)(data_id & 0xFF); out[7] = (uint8_t)((data_id >> 8) & 0xFF); out[8] = 1; for (int i = 9; i < 16; i++) out[i] = 0x00; out += in; } private: std::unique_ptr mHeartbeat_; using HANDLE_FUNC = std::function; std::unordered_map> mHandleMap_; bool ParseJson(const char* buf, int len, json& document, int& type, int& command, json& items) { if (!buf || len == 0) return false; try { document = json::parse(buf, buf + len); if (!document.is_object() || !document.contains("PatrolDevice")) throw std::runtime_error("No PatrolDevice"); json& root = document["PatrolDevice"]; if (!root.is_object()) throw std::runtime_error("PatrolDevice not object"); if (!root.contains("Items")) throw std::runtime_error("No Items"); items = root["Items"]; if (!root.contains("Type") || !root.contains("Command")) throw std::runtime_error("No Type/Command"); if (!root["Type"].is_number_integer() || !root["Command"].is_number_integer()) throw std::runtime_error("Type/Command not Int"); type = root["Type"].get(); command = root["Command"].get(); } catch (const json::parse_error& e) { std::cerr << "JSON Parse Error: " << e.what() << std::endl; return false; } catch (const std::exception& e) { std::cerr << "JSON Logic Error: " << e.what() << std::endl; return false; } return true; } void JSONRootToBroker(const int& type, const int& command, json& root, bool isNew = false) { if (isNew) { root = json::object(); root["PatrolDevice"] = { {"Items", json::object()}, {"Type", type}, {"Command", command}, {"Time", getCurrentDateTime()}}; } else { root["PatrolDevice"]["Type"] = type; root["PatrolDevice"]["Command"] = command; root["PatrolDevice"]["Time"] = getCurrentDateTime(); } } std::string buildResponse(const CmdInfo& cmd_info, const int& errorCode, std::string errorMsg = "") { std::string res; json document; JSONRootToBroker(cmd_info.type, cmd_info.command, document, true); document["PatrolDevice"]["Items"]["ErrorCode"] = errorCode; if (!errorMsg.empty()) { document["PatrolDevice"]["Items"]["ErrorMessage"] = errorMsg; } res = document.dump(); return res; } void Handle_Heartbeat(json& items, std::string& res, const CmdInfo& cmd_info) { NetInfo netInfo; netInfo.ip = cmd_info.ip; netInfo.port = cmd_info.port; netInfo.fromType = cmd_info.fromType; mHeartbeat_->wakeUp(netInfo); res = buildResponse(cmd_info, 0); } void Handle_Steer(json& items, std::string& res, const CmdInfo& cmd_info) { if (!items.contains("X") || !items.contains("Y")) { res = buildResponse(cmd_info, 2, "Missing fields"); return; } try { if (items["X"].is_number()) usr_cmd_->forward_vel_scale = items["X"].get(); if (items["Y"].is_number()) usr_cmd_->side_vel_scale = items["Y"].get(); if (items.contains("Yaw") && items["Yaw"].is_number()) usr_cmd_->turnning_vel_scale = items["Yaw"].get(); } catch (const std::exception& e) { res = buildResponse(cmd_info, 3, "Data type error"); return; } static std::string LastPerIp = ""; static int64_t LastPerTime = 0; auto now = std::chrono::steady_clock::now(); auto timestamp = std::chrono::duration_cast(now.time_since_epoch()).count(); if (LastPerIp == "" || LastPerIp == cmd_info.ip) { LastPerIp = cmd_info.ip; LastPerTime = timestamp; } else if (timestamp - LastPerTime > 2) { LastPerIp = cmd_info.ip; LastPerTime = timestamp; } else { std::cout << "IP Conflict" << std::endl; res = buildResponse(cmd_info, 4, "IP Conflict"); return; } } void Handle_Key(json& items, std::string& res, const CmdInfo& cmd_info) { static std::unordered_map keyMap = {{"G20_KEY_L1", KeyCode::L1}, {"G20_KEY_L2", KeyCode::L2}, {"G20_KEY_R1", KeyCode::R1}, {"G20_KEY_R2", KeyCode::R2}}; if (items.contains("KeyList") && items["KeyList"].is_array()) { for (const auto& key : items["KeyList"]) { if (key.is_string()) { std::string keyStr = key.get(); if (keyMap.count(keyStr)) { KeyCode code = keyMap[keyStr]; switch (code) { case KeyCode::L1: if (msfb_->GetCurrentState() == RobotMotionState::WaitingForStand || msfb_->GetCurrentState() == RobotMotionState::LieDown) { usr_cmd_->target_mode = uint8_t(RobotMotionState::StandingUp); std::cout << "[MODE] Standing Up\n"; } break; case KeyCode::L2: if (msfb_->GetCurrentState() == RobotMotionState::StandingUp) { usr_cmd_->target_mode = uint8_t(RobotMotionState::RLControlMode); std::cout << "[MODE] RL Control\n"; } break; case KeyCode::R1: if (msfb_->GetCurrentState() == RobotMotionState::StandingUp || msfb_->GetCurrentState() == RobotMotionState::RLControlMode) { usr_cmd_->target_mode = uint8_t(RobotMotionState::LieDown); std::cout << "[MODE] Lie Down\n"; } break; case KeyCode::R2: usr_cmd_->target_mode = uint8_t(RobotMotionState::JointDamping); std::cout << "[MODE] Joint Damping\n"; break; default: break; } } } } } res = buildResponse(cmd_info, 0); } std::string getCurrentDateTime() { auto now = std::chrono::system_clock::now(); std::time_t currentTime = std::chrono::system_clock::to_time_t(now); std::tm* localTime = std::localtime(¤tTime); auto ms = std::chrono::duration_cast(now.time_since_epoch()) % 1000; std::stringstream ss; ss << std::put_time(localTime, "%Y-%m-%d %H:%M:%S") << "." << std::setw(3) << std::setfill('0') << ms.count(); return ss.str(); } };