/** * @file hand_record.cpp * @brief Record & replay named hand poses (12 joint values: right 0-5, left 6-11). * * You sculpt a shape live (close/open individual fingers), name it, and it's saved * to a file. Records the COMMANDED values (the serial read-back is unreliable on * these CH340 adapters, and the hand holds whatever it's commanded anyway). * Needs ./inspire_g1 running. * * ./hand_record [iface] interactive recorder (default iface eth0) * ./hand_record play [iface] command a saved pose and hold * ./hand_record list list saved poses * * Poses file: $HOME/DFX_inspire_service/hand_poses.txt (line: "name v0 .. v11") * Per-hand joint order: pinky ring middle index thumb_bend thumb_rot. q in [0,1]. */ #include #include #include #include #include #include #include #include #include #include #include using Pose = std::array; static std::string posesFile() { const char *h = getenv("HOME"); return std::string(h ? h : ".") + "/DFX_inspire_service/hand_poses.txt"; } static std::map loadPoses() { std::map m; std::ifstream f(posesFile()); std::string line; while (std::getline(f, line)) { if (line.empty() || line[0] == '#') continue; std::istringstream iss(line); std::string name; iss >> name; Pose v{}; int i = 0; float x; while (i < 12 && (iss >> x)) v[i++] = x; if (i == 12) m[name] = v; } return m; } static void savePose(const std::string &name, const Pose &v) { auto m = loadPoses(); m[name] = v; // add or overwrite std::ofstream f(posesFile(), std::ios::trunc); f << "# name \n"; for (const auto &kv : m) { f << kv.first; for (float x : kv.second) f << " " << x; f << "\n"; } } class Hand { public: Hand() { pub_ = std::make_shared>("rt/inspire/cmd"); pub_->InitChannel(); cmd_.cmds().resize(12); } void apply(const Pose &v) { for (int i = 0; i < 12; i++) cmd_.cmds()[i].q() = v[i]; for (int n = 0; n < 8; n++) { pub_->Write(cmd_); usleep(20000); } // ~0.16s so it lands } private: unitree_go::msg::dds_::MotorCmds_ cmd_; unitree::robot::ChannelPublisherPtr pub_; }; static void printPose(const Pose &v) { std::cout << " R:"; for (int i = 0; i < 6; i++) std::cout << " " << v[i]; std::cout << " L:"; for (int i = 6; i < 12; i++) std::cout << " " << v[i]; std::cout << std::endl; } static void printMenu() { std::cout << "\n--- hand_record --- q in [0,1] (tune to taste), order: pinky ring middle index thumb_bend thumb_rot\n" " r <6 nums> set RIGHT hand l <6 nums> set LEFT hand b <6 nums> set BOTH\n" " f <0-11> set one joint (0-5 right, 6-11 left) open | close all\n" " save save current shape load recall list | print | q\n"; } int main(int argc, char **argv) { std::string a1 = argc > 1 ? argv[1] : ""; if (a1 == "list") { auto m = loadPoses(); std::cout << m.size() << " saved pose(s):\n"; for (const auto &kv : m) { std::cout << " " << kv.first << ":"; printPose(kv.second); } return 0; } if (a1 == "play") { if (argc < 3) { std::cerr << "usage: hand_record play [iface]\n"; return 1; } std::string name = argv[2], iface = argc > 3 ? argv[3] : "eth0"; auto m = loadPoses(); if (!m.count(name)) { std::cerr << "no pose '" << name << "'. try: hand_record list\n"; return 1; } unitree::robot::ChannelFactory::Instance()->Init(0, iface); Hand hand; usleep(300000); std::cout << "playing '" << name << "'\n"; printPose(m[name]); hand.apply(m[name]); usleep(1500000); return 0; } // interactive std::string iface = a1.empty() ? "eth0" : a1; unitree::robot::ChannelFactory::Instance()->Init(0, iface); Hand hand; usleep(300000); Pose cur; cur.fill(1.0f); // start open hand.apply(cur); printMenu(); std::string line; while (true) { std::cout << "rec> " << std::flush; if (!std::getline(std::cin, line)) break; std::istringstream iss(line); std::string op; iss >> op; if (op.empty()) continue; if (op == "q" || op == "quit") break; else if (op == "open") { cur.fill(1.0f); hand.apply(cur); printPose(cur); } else if (op == "close") { cur.fill(0.0f); hand.apply(cur); printPose(cur); } else if (op == "print") { printPose(cur); } else if (op == "list") { for (const auto &kv : loadPoses()) { std::cout << " " << kv.first << ":"; printPose(kv.second); } } else if (op == "r" || op == "l" || op == "b") { std::vector v; float x; while (iss >> x) v.push_back(x); if (v.size() != 6) { std::cout << " need 6 numbers\n"; continue; } if (op == "r" || op == "b") for (int i = 0; i < 6; i++) cur[i] = v[i]; if (op == "l" || op == "b") for (int i = 0; i < 6; i++) cur[i + 6] = v[i]; hand.apply(cur); printPose(cur); } else if (op == "f") { int idx; float val; if (!(iss >> idx >> val) || idx < 0 || idx > 11) { std::cout << " usage: f <0-11> \n"; continue; } cur[idx] = val; hand.apply(cur); printPose(cur); } else if (op == "save") { std::string name; iss >> name; if (name.empty()) { std::cout << " usage: save \n"; continue; } savePose(name, cur); std::cout << " saved '" << name << "' -> " << posesFile() << "\n"; } else if (op == "load") { std::string name; iss >> name; auto m = loadPoses(); if (!m.count(name)) { std::cout << " no pose '" << name << "'\n"; continue; } cur = m[name]; hand.apply(cur); printPose(cur); } else std::cout << " ? see menu\n"; } std::cout << "bye.\n"; return 0; }