/** * @file hand_setid.cpp * @brief Give a hand a distinct RS-485 id, permanently, so left/right can never swap again. * * WHY THIS EXISTS * The RH56 has no handedness register — it cannot tell you whether it is a left or a right * unit. Both USB-RS485 dongles are CH340 1a86:7523 reporting the SAME USB serial. So the * only thing distinguishing the two hands was the ttyUSB number, which the kernel hands out * in enumeration order — and that changes across replugs, reboots and hub hiccups. When it * flips, left and right silently trade places: nothing errors, and you drive the wrong hand. * (Observed on this robot 2026-07-30: software "L.*" was in fact the robot's RIGHT hand.) * * Giving the two hands DIFFERENT ids fixes that at the root. inspire_g1 scans ids 1..4 on * each port and, when the ids differ, uses them as identity: id 1 = RIGHT, id 2 = LEFT. * That is wiring-independent and survives any replug. * * USAGE — run ONCE per robot, with the OTHER hand's dongle unplugged or on its own port: * g++ -std=c++17 -Iinclude example/hand_setid.cpp -o build/hand_setid * ./build/hand_setid /dev/ttyUSBx # just report which id answers * ./build/hand_setid /dev/ttyUSBx 1 2 # change id 1 -> 2 and SAVE it * * Convention: leave the RIGHT hand at id 1, give the LEFT hand id 2. * Needs the port free — stop the container first. * * Registers: HAND_ID 1000 (0x03E8), SAVE 1005 (0x03ED). The id change only survives a * power cycle if SAVE is written afterwards, which this does. */ #include "inspire.h" #include #include #include #include #include static uint8_t cks(const uint8_t *d, int len) { uint8_t s = 0; for (int i = 2; i < len - 1; i++) s += d[i]; return s; } // Write the SAVE register (1005) so the new id survives a power cycle. static void saveParams(SerialPort &sp, uint8_t id) { uint8_t c[9] = {0xEB, 0x90, id, 0x04, 0x12, 0xED, 0x03, 0x01, 0x00}; c[8] = cks(c, 9); sp.send(c, 9); usleep(300000); uint8_t b[32]; sp.recv(b, sizeof(b)); } // Which id answers on this port? -1 if none. static int findId(SerialPort::SharedPtr sp) { for (int id = 1; id <= 4; id++) { inspire::InspireHand h(sp, (uint8_t)id); Eigen::Matrix q; for (int t = 0; t < 3; t++) if (h.GetPosition(q) == 0) return id; } return -1; } int main(int argc, char **argv) { if (argc < 2) { printf("usage: %s [from_id to_id]\n" " %s /dev/ttyUSB0 report the id that answers\n" " %s /dev/ttyUSB0 1 2 change id 1 -> 2 and save\n", argv[0], argv[0], argv[0]); return 1; } const char *dev = argv[1]; auto sp = std::make_shared(dev, B115200, 8); int cur = findId(sp); if (cur < 0) { printf("%s: no hand answered on ids 1-4.\n" " Is the container still holding the port? Is the hand powered?\n", dev); return 2; } printf("%s: hand answers at rs485 id %d\n", dev, cur); if (argc < 4) return 0; int from = atoi(argv[2]), to = atoi(argv[3]); if (from != cur) { printf("refusing: you said from=%d but this hand is at id %d.\n", from, cur); return 3; } if (to < 1 || to > 4) { printf("refusing: to_id must be 1-4.\n"); return 3; } printf("changing id %d -> %d ...\n", from, to); inspire::InspireHand h(sp, (uint8_t)from); h.ChangeID((uint8_t)from, (uint8_t)to); usleep(300000); saveParams(*sp, (uint8_t)to); usleep(300000); int now = findId(sp); if (now == to) printf("OK — this hand is now id %d and it is saved.\n" " Convention: RIGHT hand stays id 1, LEFT hand becomes id 2.\n" " inspire_g1 will now identify the hands by id, and left/right can no longer\n" " swap however the USB ports enumerate.\n", now); else printf("FAILED — hand now answers at id %d (wanted %d). Nothing saved reliably;\n" " power-cycle the hand and re-check with: %s %s\n", now, to, argv[0], dev); return now == to ? 0 : 4; }