/** * @file hand_diag.cpp * @brief Read the Inspire RH56 fault registers per finger: ERROR, STATUS, TEMP, * CURRENT. Tells you exactly WHY a finger won't move (locked rotor, * over-temperature, over-current, ...) instead of guessing. * * Register map (from the Inspire Modbus map; this serial protocol encodes the * address as low,high in bytes 5,6 — verified: CLEAR_ERROR 1004 = 0x03EC = {0xEC,0x03}): * CURRENT 1594 (0x063A, 12 bytes) ERROR 1606 (0x0646, 6) * STATUS 1612 (0x064C, 6) TEMP 1618 (0x0652, 6) * CLEAR_ERROR 1004 (0x03EC) RESET_PARA 1006 (0x03EE) * * g++ -std=c++17 -Iinclude example/hand_diag.cpp -o /tmp/hand_diag * /tmp/hand_diag /dev/ttyUSB1 [clear] # 'clear' also sends CLEAR_ERROR * * Needs the port free (stop the container first). */ #include "SerialPort.h" #include #include #include #include #include #include static const char *FING[6] = {"pinky", "ring", "mid", "index", "thumbB", "thumbR"}; static uint8_t cksum(const uint8_t *d, int len) { uint8_t s = 0; for (int i = 2; i < len - 1; i++) s += d[i]; return s; } // Read `n` bytes from register `addr`, robustly (flush + re-query). static bool readReg(SerialPort &sp, uint16_t addr, uint8_t n, uint8_t *out, uint8_t id = 1) { uint8_t q[9] = {0xEB, 0x90, id, 0x04, 0x11, (uint8_t)(addr & 0xFF), (uint8_t)(addr >> 8), n, 0x00}; q[8] = cksum(q, 9); uint8_t buf[128]; for (int att = 0; att < 8; att++) { sp.flush(); sp.send(q, 9); usleep(8000); size_t got = sp.recv(buf, sizeof(buf)); for (size_t off = 0; off + 7 + n <= got; off++) { if (!((buf[off] == 0x90 && buf[off + 1] == 0xEB) || (buf[off] == 0xEB && buf[off + 1] == 0x90))) continue; if (buf[off + 4] != 0x11) continue; uint16_t got_addr = buf[off + 5] | (buf[off + 6] << 8); if (got_addr != addr) continue; int frame = 7 + n + 1; // header+addr+data+checksum if (off + frame > got) continue; if (buf[off + frame - 1] != cksum(buf + off, frame)) continue; memcpy(out, buf + off + 7, n); return true; } } return false; } static void writeReg16(SerialPort &sp, uint16_t addr, uint16_t val, uint8_t id = 1) { uint8_t c[11] = {0xEB, 0x90, id, 0x06, 0x12, (uint8_t)(addr & 0xFF), (uint8_t)(addr >> 8), (uint8_t)(val & 0xFF), (uint8_t)(val >> 8), 0x00, 0x00}; c[9] = cksum(c, 10); sp.send(c, 10); usleep(5000); uint8_t b[32]; sp.recv(b, sizeof(b)); } static std::string errStr(uint8_t e) { if (!e) return "OK"; std::string s; if (e & 0x01) s += "LOCKED_ROTOR "; if (e & 0x02) s += "OVER_TEMPERATURE "; if (e & 0x04) s += "OVER_CURRENT "; if (e & 0x08) s += "ABNORMAL_OPERATION "; if (e & 0x10) s += "COMMUNICATION_ERROR "; return s; } // Command all 6 fingers to a position (ANGLE_SET 1486 = 0x05CE, 12 bytes). static void setAll(SerialPort &sp, int16_t val, uint8_t id = 1) { uint8_t c[20]; c[0] = 0xEB; c[1] = 0x90; c[2] = id; c[3] = 0x0F; c[4] = 0x12; c[5] = 0xCE; c[6] = 0x05; for (int i = 0; i < 6; i++) { c[7 + 2 * i] = val & 0xFF; c[8 + 2 * i] = (val >> 8) & 0xFF; } c[19] = cksum(c, 20); sp.send(c, 20); usleep(5000); uint8_t b[32]; sp.recv(b, sizeof(b)); } // Write all 6 force limits (FORCE_SET 1498 = 0x05DA, 12 bytes). static void setForceAll(SerialPort &sp, uint16_t val, uint8_t id = 1) { uint8_t c[20]; c[0] = 0xEB; c[1] = 0x90; c[2] = id; c[3] = 0x0F; c[4] = 0x12; c[5] = 0xDA; c[6] = 0x05; for (int i = 0; i < 6; i++) { c[7 + 2 * i] = val & 0xFF; c[8 + 2 * i] = (val >> 8) & 0xFF; } c[19] = cksum(c, 20); sp.send(c, 20); usleep(5000); uint8_t b[32]; sp.recv(b, sizeof(b)); } // Does the motor actually energize when told to move? current>0 => it's trying // (mechanically blocked); current==0 => it never energizes. // // IMPORTANT: current==0 alone does NOT mean the actuator is dead. The RH56 stops driving a // finger the instant its MEASURED force (FORCE_ACT 1582) reaches its force LIMIT // (FORCE_SET 1498), so a finger whose force-sensor zero has drifted above the limit draws // 0 mA and is indistinguishable from a dead motor. This test used to inherit whatever limit // the service last wrote (500g) and so reproduced exactly that failure while looking like an // independent raw-serial check — it is how the G1's right mid+index were wrongly called dead // when they in fact run their full range at a 1000g limit. It now sweeps the limit itself. static void moveTest(SerialPort &sp, uint16_t force) { printf("\n=== MOVE TEST: force limit %ug, command CLOSE, watch current + force + angle ===\n", force); setForceAll(sp, force); setAll(sp, 1000); // open first usleep(800000); uint8_t f0[12] = {0}; if (readReg(sp, 1582, 12, f0)) { printf("resting force vs the %ug limit (>= limit => the driver refuses to move it):\n ", force); for (int i = 0; i < 6; i++) { int16_t F = (int16_t)(f0[2 * i] | (f0[2 * i + 1] << 8)); printf("%s=%dg%s ", FING[i], F, (F >= (int)force || -F >= (int)force) ? "<", "hand_diag "); } // Are the dead channels merely CONFIGURED not to drive? A speed / force / current // limit of 0 would give exactly the observed signature: no motion, no current, no // error, status "reached target". That would be software-fixable. static void paramDump(SerialPort &sp) { struct { const char *name; uint16_t addr; uint8_t bytes; } regs[] = { {"ANGLE_SET ", 1486, 12}, {"FORCE_SET ", 1498, 12}, {"SPEED_SET ", 1522, 12}, {"CURRENT_LIM ", 1020, 12}, {"ANGLE_ACT ", 1546, 12}, }; printf("%-13s", "register"); for (int i = 0; i < 6; i++) printf("%9s", FING[i]); printf("\n"); for (auto &r : regs) { uint8_t b[16] = {0}; bool ok = readReg(sp, r.addr, r.bytes, b); printf("%-13s", r.name); for (int i = 0; i < 6; i++) { if (!ok) { printf("%9s", "FAIL"); continue; } printf("%9d", (int16_t)(b[2 * i] | (b[2 * i + 1] << 8))); } printf("\n"); } printf("\n(a 0 in SPEED_SET / FORCE_SET / CURRENT_LIM for a finger would explain\n" " no motion + no current + no error, and would be software-fixable.)\n"); } int main(int argc, char **argv) { const char *dev = argc > 1 ? argv[1] : "/dev/ttyUSB1"; std::string opt = argc > 2 ? argv[2] : ""; bool doClear = (opt == "clear"); bool doMove = (opt == "move"); SerialPort sp(dev, B115200, 8); if (opt == "params") { paramDump(sp); return 0; } // Command a CLOSE, then read ANGLE_SET back: does the setpoint actually LAND in // the dead channels? ANGLE_SET=0 but no motion => setpoint accepted, motor dead. // ANGLE_SET stays 1000 => the channel rejects the write. if (opt == "setclose") { printf("commanding CLOSE (ANGLE_SET=0) ...\n\n"); setAll(sp, 0); usleep(1500000); paramDump(sp); setAll(sp, 1000); return 0; } // 'move [force]' — force defaults to the RH56 max so a drifted sensor zero can't // silently gate a healthy finger off and make it look dead. if (doMove) { uint16_t f = argc > 3 ? (uint16_t)atoi(argv[3]) : 1000; moveTest(sp, f); return 0; } if (doClear) { printf("sending CLEAR_ERROR (1004) ...\n"); writeReg16(sp, 1004, 1); usleep(300000); } uint8_t err[6] = {0}, sta[6] = {0}, tmp[6] = {0}, cur[12] = {0}; uint8_t fact[12] = {0}, fset[12] = {0}; bool eok = readReg(sp, 1606, 6, err); bool sok = readReg(sp, 1612, 6, sta); bool tok = readReg(sp, 1618, 6, tmp); bool cok = readReg(sp, 1594, 12, cur); bool faok = readReg(sp, 1582, 12, fact); // FORCE_ACT — measured bool fsok = readReg(sp, 1498, 12, fset); // FORCE_SET — the limit printf("port %s (error=%s status=%s temp=%s current=%s force=%s limit=%s)\n\n", dev, eok ? "ok" : "FAIL", sok ? "ok" : "FAIL", tok ? "ok" : "FAIL", cok ? "ok" : "FAIL", faok ? "ok" : "FAIL", fsok ? "ok" : "FAIL"); printf("%-8s %6s %8s %6s %9s %8s %7s %s\n", "finger", "err", "status", "temp", "current", "force", "limit", "meaning"); bool anyGated = false; for (int i = 0; i < 6; i++) { int16_t c = cok ? (int16_t)(cur[2 * i] | (cur[2 * i + 1] << 8)) : 0; int16_t fa = faok ? (int16_t)(fact[2 * i] | (fact[2 * i + 1] << 8)) : 0; int16_t fs = fsok ? (int16_t)(fset[2 * i] | (fset[2 * i + 1] << 8)) : 0; // THE check that was missing: a finger already at/over its force limit is gated off // by the driver and will read 0 current with no error — identical to a dead motor. bool gated = faok && fsok && fs > 0 && (fa >= fs || -fa >= fs); if (gated) anyGated = true; std::string mean = eok ? errStr(err[i]) : "?"; if (gated) mean = "FORCE-GATED (not dead: force >= limit)"; printf("%-8s 0x%02X %8d %5d\xC2\xB0 %8d %7dg %6dg %s\n", FING[i], eok ? err[i] : 0, sok ? sta[i] : -1, tok ? tmp[i] : -1, c, fa, fs, mean.c_str()); } if (anyGated) printf("\n>> One or more fingers is FORCE-GATED: its measured force already meets its\n" ">> limit, so the driver refuses to energise it (0 current, no error). This is a\n" ">> drifted force-sensor zero, NOT a dead actuator. Confirm with:\n" ">> %s %s move 1000\n" ">> Fix by re-zeroing the sensor with the hand unloaded, or raise the limit.\n", argv[0], dev); return 0; }