Hardware verification — servomotor-mcp 0.3.0 — 2026-07-03
Bench: physical M17, fw 0.15.3.0, alias 88, uid 99856389A2B46555, ~20 V supply.
Host: macOS (darwin), 4x CP2102N USB-RS485 adapters, motor on one of them.

hw_discover.py (port sweep)
  /dev/cu.usbserial-110  -> No motors responded on this bus (graceful note)
  /dev/cu.usbserial-1420 -> No motors responded on this bus (graceful note)
  /dev/cu.usbserial-1430 -> No motors responded on this bus (graceful note)
  /dev/cu.usbserial-210  -> [{"alias": 88, "unique_id": "99856389A2B46555"}]
  Bluetooth/debug ports enumerated but flagged likely_usb_serial_adapter=false.

hw_suite.py: 49 passed, 0 failed. Highlights:
  list_motors: position 0.0 deg, 20.0 V, 41 C, status clean
  all read-only commands OK (product info "M17", hw 1.5.0, sn 1330, fw 0.15.3.0,
    specs 31250 Hz / 3276800 counts/rot, hall stats, comm stats, debug values)
  ping echo "hello mcp" OK
  move_to 360 -> settled 360.0; move_relative -90 -> 270.0; move by unique id -> 0.0
  raw go_to_position 90 over 1.5 s -> queue drained -> 89.99978
  raw trapezoid_move -45 (relative) -> 44.99989
  multimove [accel +200 dps^2 1 s, accel -200 dps^2 1 s] -> no fatal error
  vibrate on/off, identify, reset_time -> current time 0.098 s, zero_position -> 0.0
  set_maximum_velocity 600 dps, set_maximum_acceleration 10000 dps^2 OK
  emergency_stop, stop() broadcast, disable_mosfets OK
  system_reset -> rebooted, status clean afterwards
  run_sequence (move_to + wait + move_relative + raw command + stop) -> 45.0
  error paths: absent motor 99 -> actionable timeout message; wrong arg count -> clear error
  re-detect finds the motor again; clean disconnect

hw_mcp_stdio_session.py (official mcp client -> subprocess server, stdio): 10/10
  clean initialize handshake (library chatter on stderr; stdout pure JSON-RPC)
  57 tools listed (47 generated from the command catalog + 10 high-level)
  connect to empty adapter -> graceful "No motors" note
  connect to right adapter -> auto-detected alias 88
  move_to 360 via MCP; raw get_temperature via generated tool
  run_sequence incl. raw command step; get_motor_status healthy
  connect to nonexistent port -> error lists available ports, keeps old connection
  stop-all still works after the failed connect; clean disconnect

uvx launch (Claude Desktop mechanism):
  uvx --from '<local path>[serial]' servomotor-mcp -> 57 tools, connect+detect,
  moves +30/-30 deg OK.

Unit tests (mock backend, no hardware): 39 passed.
