Blackndoerobotvisiondemo2-github.mp4
Generic robot setup nodes for Blacknode.
This is a Blacknode extension package — it does not run on its own. It plugs robot hardware setup into the Blacknode visual workflow editor: find USB serial robot devices, fix Linux serial permissions, launch and stop a driver process, and emit reusable robot profiles for downstream control nodes — drivable from workflows or AI agents over MCP.
This package owns the user-facing robot abstraction:
- find USB serial robot devices
- explain Linux serial permissions
- describe how a robot driver should start
- start/stop a driver process
- build, save, duplicate, load, and calibrate reusable robot profiles
It intentionally does not know every robot protocol. Robot-specific packages
provide driver descriptors and hardware bridges. Transport packages such as
blacknode-ros2 verify and control the standard interface exposed by the
driver.
Coding agents should read AGENTS.md before changing this package.
It defines the package boundary, calibration identity contract, motion safety,
and verification commands.
| Node | What it does |
|---|---|
RobotUSBDiscovery |
Finds Windows COM* and Linux USB-serial adapters, exposes copyable port and serial outputs, and reports access fixes |
RobotDriverDescriptor |
Declares a driver command template and standard topics |
Robot |
Selects a built-in or saved robot and applies the connected device's calibration |
RobotJointDefinition |
Defines one named joint, servo ID, range, zero, and direction |
RobotJointList |
Combines any number of joint definitions; another socket appears as the list fills |
RobotDefinition |
Builds a reusable robot profile and driver contract visually |
RobotProfileSave |
Saves a profile under robots/<profile_id>/profile.json |
RobotProfileLoad |
Loads a profile and the calibration for a connected hardware ID |
RobotProfileList |
Lists built-in and locally saved profiles |
RobotProfileDuplicate |
Copies a built-in or local profile under a new editable name |
RobotCalibrationRecorder |
Safely records released-arm limits and a home pose for one physical robot |
RobotDriverLauncher |
Starts/stops a driver process from the descriptor |
RobotDiscovery |
Runs the generic setup path and outputs a robot profile |
RobotConnectionDashboard |
Shows USB, driver, ROS interface, live joint positions, home references, safe ranges, and calibration source in one view |
Changing the generic Robot.profile_id invalidates the old dashboard. Press
Run to apply it: if its generated driver command differs, Blacknode safely
stops the prior managed process before starting the selected profile. A
PROFILE DEFAULTS dashboard has no saved calibration for that profile and
hardware ID.
The SO-ARM101 Leader Follower template runs two Robot selectors with
separate USB serial filters, driver run IDs, and /leader and /follower ROS
topic prefixes. It releases only the leader, starts the follower controller in
disarmed preview, and requires saved calibration for both physical devices.
Its default configuration matches LeRobot teleoperation: tracking_mode=direct
at 60 Hz with no deadband or relative step limiter. The Feetech driver batches
all joint reads and all goal writes into synchronized bus transactions, while
calibration limits, stale-stream suppression, and explicit arming still apply.
The generic pipeline is:
USB device -> driver descriptor -> driver process -> standard robot profile
Driver descriptors use {serial_port} as the placeholder for the discovered
USB path:
python scripts/my_robot_driver.py --port {serial_port}
The resulting robot profile carries:
state_topiccommand_topic- optional
config_topic usbdriverinterface
Robot-specific packages should fill in the descriptor. Transport packages should verify and use the interface.
Use the generic Robot node in new workflows. Its dropdown includes built-in
and locally saved profiles, and its driver output connects directly to
RobotDiscovery or RobotDriverLauncher. RobotDriverPreset and
RobotProfileLoad remain hidden compatibility aliases for old workflows.
RobotDefinition.driver_script is also a dropdown populated from installed
drivers/*_driver.py files when Blacknode starts. Adding a driver file and
restarting Blacknode makes it selectable without changing the node. A custom
executable or non-Python launch path can still use protocol=custom with an
explicit command_template.
The curated SO-ARM101 preset uses the same profile schema as a visual custom robot. Supporting another arm on the bundled Feetech protocol normally means assembling joint nodes and saving a profile; it does not require changing Python. Only a genuinely new wire protocol needs another bus driver.
Open Editable SO-ARM101 Profile as a working example. Each
RobotJointDefinition names a stable joint and sets its servo ID, provisional
range, center tick, and direction. RobotJointList preserves their order;
RobotDefinition creates the profile; and RobotProfileSave makes it reusable.
To reuse the same mechanical definition under another identity, use
RobotProfileDuplicate with source_profile_id=so_arm101 and choose a new ID.
For structural changes, copy the Editable SO-ARM101 Profile workflow and
edit its visible joint nodes before saving. Profile and joint IDs normalize to lowercase
snake_case, limited to 64 characters, and must be unique. Display names are
free-form and can change without breaking workflows.
Connect RobotUSBDiscovery.recommended to RobotDefinition.hardware. The
definition automatically copies the real USB vendor ID and product ID reported
by the adapter; these are four-digit hexadecimal identifiers assigned to the
hardware manufacturer/product, not random robot IDs. The manual vendor_id and
product_id fields are optional advanced overrides. Saved VID/PID values help
RobotDiscovery reject unrelated serial adapters, while the adapter serial (or
device path when no serial exists) selects the calibration for one physical
assembly. Normally users should leave hardware_id blank and connect discovery
to the hardware input.
Local robot data is deliberately separate from the package source:
robots/
my_robot/
profile.json
calibrations/
usb_serial_or_device_id.json
Set BLACKNODE_ROBOTS_DIR to move this library elsewhere. The default
robots/ directory is ignored by Git because calibrations describe a specific
physical assembly. Copy or version it deliberately when sharing a machine
configuration.
Open Robot Guided Calibration after saving a profile:
- Load the profile and start discovery with the robot connected.
- Press Release + live pose on Manual Move and physically support the arm.
- Confirm live joint values are changing, then press Start recording.
- Slowly move every joint through the safe physical range you intend to use. Do not force a hard stop.
- Put the robot in the pose that should read as zero and press Capture Home.
- Press Stop recording whenever you want to pause extrema collection without losing samples. Current pose remains live; press Resume recording to continue.
- Press Save calibration. The recorder applies the configured safety margin inside the observed extrema and saves it under the hardware ID.
- Press Hold position only while the arm is supported and the workspace is clear.
Recording never commands movement. It refuses to start while torque is on, and
it will not save until every configured joint has been observed and a home pose
has been captured. Its CURRENT pose, observed ranges, sample count, dashboard,
report, and connected Output nodes update through the live runtime. Robot
automatically applies the matching
device calibration when given the discovery hardware output; another physical
robot with the same profile keeps a separate calibration.
While recording, the most strongly moving joint is labeled CAPTURING. Its
row turns blue, and a newly extended limit flashes amber with MIN ↓, MAX ↑,
or RANGE. This distinguishes ordinary motion inside an already observed range
from a sample that actually changed the saved extrema.
Drives a real SO-ARM101 —
6x Feetech STS3215 serial-bus servos (shoulder_pan, shoulder_lift,
elbow_flex, wrist_flex, wrist_roll, gripper; servo IDs 1-6; 1 Mbps)
— through drivers/feetech_bus_driver.py. The preset defaults to
transport=auto: it uses native rclpy when available and otherwise uses
rosbridge. The transport can still be forced for advanced deployments. In
rosbridge mode the serial connection remains local to the driver machine.
pip install -r packages/blacknode-robot/requirements.txt # servo SDK + roslibpy- Plug in the arm, then run
RobotUSBDiscoveryto confirm its USB-serial adapter is found and accessible (fix any permission issue it reports first). This confirms the adapter is enumerated, not that robot power or servo communication is healthy; use the driver connection state for that. - Load the SO-ARM101 Motion Test template
(
templates/so-arm101-motion-test.json):RobotDriverPreset(presetso_arm101) →RobotDiscovery(starts the driver process) →ROS2Status→ROS2JointState→ROS2SetJoint(armed=falseby default) →ROS2MotionDashboard. - Press Run. With
armed=falsethis only proves the pipeline: the driver starts,ROS2Statusselects native ROS 2 or rosbridge, andROS2JointStateshows the arm's live pose. The arm must not move or twitch during this step — see Safety below for why. - Set a
jointname andarmed=trueonROS2SetJoint, recook. It syncs to the current pose, ramps to the target, and the dashboard shows before/after.
The motion-test template includes ROS2ManualMove between connection status and
motion. Its safe default is Monitor only, which changes no torque state and
starts an explicitly labeled live pose monitor.
- Press Release + live pose to disable servo torque while the driver keeps publishing joint positions. Support the arm before pressing it; it may go limp.
- Move the supported arm by hand. The Teach node's unconnected dashboard output refreshes from the runtime monitor and shows the latest pose.
- Press Hold position to hold again. The driver reads every servo and writes those exact positions as goals before enabling torque. Motion is blocked during the transition. The selected button and dashboard report the actual current mode rather than only the last requested action.
- Stop all remains the emergency-safe shutdown: it stops the driver and disables torque, so live state publishing also stops.
- Torque-enable sequencing. Feetech STS servos snap toward whatever is
already sitting in
Goal_Positionthe instantTorque_Enableswitches on — that register is not guaranteed to already equal the physical position.feetech_bus_driver.pyalways reads every servo's current position first, writes that same value back asGoal_Positionwhile torque is still off, and only then enables torque — so there is nothing for the servo to snap toward. If the arm moves or twitches the moment the driver starts, stop and investigate before arming anything. - Shutdown behavior. By default the driver disables torque on every
servo when it stops (clean shutdown, crash, or
RobotDriverLauncheraction=stop) — the arm goes limp rather than holding its last position indefinitely with no watchdog. Override with--no-torque-off-on-exitin the preset'scommand_template(or a customRobotDriverDescriptor) only if holding position is actually the safer failure mode for your specific mounting. - Rosbridge reconnects without restarting the hardware driver. A dropped
WebSocket no longer leaves an alive-but-silent process. The driver keeps the
serial session open, waits for
roslibpyto reconnect, then republishes its safety configuration and current joint pose before resuming state updates. Command writes and state reads share one bus lock so Feetech packet transactions cannot overlap. Confirmed command writes consume each servo's status response, and malformed short read packets retain the last valid pose instead of terminating the driver. Late driver exits retain their error text in runtime status instead of disappearing as a generic offline process. - The editor's "Stop all" reaches the driver process too.
robot.pyexposesruntime_status()/stop_runtime_services()(registered in the main Blacknode editor's_RUNTIME_MODULES), so pressing "Stop all" sendsSIGTERMto every driver this session launched — which is what actually triggers the torque-off-on-exit shutdown above. Before this was wired in, "Stop all" only stopped camera/tracking/reasoning stream helpers and left the robot driver (and torque) running silently in the background. - Joint limits are placeholders until calibrated. The
min_deg/max_degvalues in the SO-ARM101 base profile are not a verified safe envelope for your assembly. Use Robot Guided Calibration with torque released to record intended physical ranges and a safety margin before commanding broad motion. Never discover limits by driving an armed joint into its hard stop. - Calibration details.
feetech_bus_driver.pyinitially assumes each servo's raw center tick (2048 of 4095) is that joint's mechanical zero and that none of the joints are mirror-mounted. Saved profiles carry direction and center information, while device calibrations supply measured home ticks and safe ranges automatically. The--home-ticksand--invertCLI flags remain available as advanced driver-level overrides. - Verify the control-table addresses before ever writing. Run
python packages/blacknode-robot/drivers/feetech_bus_driver.py --dry-run --port <serial_port> --joints "<name:id:min:max,...>"first: it only readsPresent_Positionfor every servo ID and prints the result — it never touchesGoal_PositionorTorque_Enable. Confirm every servo responds with a plausible tick (0-4095) before trusting the driver with real writes.
Keep a physical power cutoff within reach and clear the workspace before arming.