NoneadChina/dsh-nonead-universal-robots ↗★ 5
dsh-nonead-universal-robots
通过自然语言控制优傲机械臂 适合需要使用 DSH 智能体工具直接调试和控制 UR 机械臂的用户。
安裝
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:NoneadChina/dsh-nonead-universal-robots說明文件
閱讀完整 README ↗dsh-nonead-universal-robots
A DeepSeek Harness (DSH) plugin that lets you control Universal Robots (UR) collaborative arms directly from natural language, developed by Suzhou Nonead Robot Technology Co., Ltd. based on the same logic as the company's own nUR MCP Server. (Suzhou Nonead Robot Technology Co., Ltd.)
This plugin shares the same ancestry as the company's Nonead-Universal-Robots-MCP (see "Reference implementation" below). It exposes the "control a UR robot with AI" capability as native DSH tools: the plugin starts a persistent Python worker (which internally reuses the vendored URBasic library), and tools talk to the robot through that worker. After a single connect, you can keep issuing commands to the same IP.
⚠️ Safety notice: this plugin drives a real robotic arm directly. Always keep the robot in sight, keep the emergency stop within reach, and keep the workspace clear of people/obstacles. Treat it with the same care as granting the
bashtool. You (or the model) bear full responsibility for any motion command.
🛡️ Motion approval gate: commands that physically move the arm, run a program, take the arm out of program control, or remove its rigidity —
ur_movej/ur_movel/ur_movep/ur_movec/ur_servoj/ur_move_optimized/ur_move_x|y|z/ur_move_tool_x|y|z/ur_draw_*/ur_load_program/ur_run_program/ur_send_script/ur_reset_error; (0.5.0)ur_set_freedrive/ur_set_teach_mode/ur_power_on/ur_power_off/ur_brake_release/ur_unlock_protective_stop/ur_shutdown/ur_zero_ftsensor/ur_set_conveyor_tracking; (0.6.0)ur_force_mode/ur_end_force_mode/ur_force_mode_settings/ur_speedj/ur_speedl/ur_stopj/ur_stopl/ur_set_payload_inertia, plusur_motion_version,ur_set_payloadandur_set_gravity— pause and wait for human confirmation before being sent to the robot. In an interactive deployment (approval policyask) a confirmation dialog is shown in the UI; a call made without an approval service, without an agent, or answered with anything other thanallowed-oncefails closed (the command is rejected) and never moves without approval. The default lives in code (config.requireApprovalForMotion ?? true), so it cannot be turned off by a caller that skips schema parsing. SetrequireApprovalForMotion: falseto disable this gate deliberately.test/approval-gate.test.mjsdrives the real tool registry and proves all of the above, including that an extraopargument cannot re-route a call.
Feature overview
83 tools (ur_*) in total.
| Category | Tool(s) (ur_*) | Description |
|---|---|---|
| Connection | ur_connect / ur_disconnect | Connect / disconnect a UR robot by IP |
| Status | ur_get_status | One-shot read of TCP, joints, model, serial, version, safety mode, run/program state, voltage, current, temperatures, uptime, joint currents/voltages/speeds, TCP speed and wrench, speed scaling |
| Pose | ur_get_tcp_pose / ur_get_joint_pose | Read current TCP pose / joint angles |
| Targets | ur_get_target_values | Read where the controller is taking the arm: target joint positions/velocities/accelerations and target TCP pose/speed, plus the matching actual values for comparison. Comes from the RTDE target_* fields (added to the receive recipe in 0.6.0), so it is free — and it is the only direct evidence for "command sent but not executed yet / being blended / held back by the safety limit" |
| Device info | ur_get_robot_model / ur_get_serial_number / ur_get_uptime / ur_get_software_version / ur_get_safety_mode / ur_get_safety_status / ur_get_robot_mode | Model (with remote_control field) / serial / uptime / software version / safety mode / safety and robot status bits (which safety function fired, incl. violation / fault; numeric limits must be read from PolyScope's Safety page) / run state |
| Programs | ur_get_program_state / ur_load_program / ur_run_program / ur_stop_program / ur_pause_program / ur_list_programs | Program state, load, run, stop, pause, SSH listing (list_programs works with the real robot's /programs and URSim ~/URSim_Linux-*/programs.*; load/run accept full/URSim paths and programs_dir). Run/stop/pause now check the controller's reply and report the run state, instead of treating "could not understand" as success |
| Registers | ur_get_int_register / ur_get_double_register / ur_get_bit_register | Read Int / Double / Bool registers |
| Health | ur_ping | Check worker/Python/URBasic readiness without a robot |
| I/O | ur_get_digital_in / ur_set_digital_out / ur_get_digital_in_bits / ur_get_digital_out_bits / ur_get_analog_in / ur_set_analog_out / ur_get_tool_analog_in | Digital in/out (incl. bit-mask reads, plus which="tool" for the tool-flange digital I/O), standard analog in/out (in engineering units: URScript's set_analog_out takes a relative level [0,1], and the old code sent 5 as full scale; full_scale may be 20 for a current-domain port), tool analog in (function name confirmed against the bundled official manuals; argument semantics not verified on hardware) |
| Tool config | ur_set_tool_voltage / ur_set_tcp / ur_set_payload / ur_set_payload_inertia / ur_set_gravity / ur_zero_ftsensor / ur_set_tool_output_mode / ur_set_tool_communication | Tool voltage (0/12/24 — the old implementation called a NotImplementedError stub and had never once succeeded), TCP, payload mass/centre of gravity, mass + CoG + inertia matrix in one call (set_target_payload, 5.10+; avoids the set_payload behaviour that resets the inertia matrix and leaves the three parameters inconsistent), gravity direction (for non-horizontal mounting), force/torque sensor zeroing, tool output mode (normal / power dual-pin), tool serial (TCI/RS-485 — ⚠️ enabling it disables the tool analog inputs) |
| Control mode / power / safety | ur_set_freedrive / ur_set_teach_mode / ur_power_on / ur_power_off / ur_brake_release / ur_unlock_protective_stop / ur_shutdown | Freedrive / teach mode (move the arm by hand), power on / off / brake release (⚠️ the arm may fall under gravity), protective-stop unlock only (no power-on, no brake release — that is what distinguishes it from ur_reset_error), controller shutdown |
| Force control | ur_force_mode / ur_end_force_mode / ur_force_mode_settings | Force Mode: the arm becomes compliant along/about the selected axes and keeps applying the requested force/torque. Arguments match the manual one by one — task_frame / selection_vector (1 = compliant) / wrench / type (1-3) / limits (compliant axes = max TCP speed, stiff axes = max deviation) / damping / gain_scaling. The script inserts the manual's recommended sleep(0.02) before entering force mode; exit with ur_end_force_mode. ⚠️ damping/gain_scaling cannot be read back from the controller, so the tool reports what it set rather than pretending to read the current value |
| Velocity control | ur_speedj / ur_speedl / ur_stopj / ur_stopl / ur_wait_steady | Joint/TCP velocity commands (speedj/speedl) and the matching decelerations (stopj/stopl). ⚠️ Velocity commands are open-ended: with t=0 (the default) the function returns once the target speed is reached while the arm keeps moving — always finish with a stop* or ur_wait_steady. ur_wait_steady polls the RTDE speed fields instead of using URScript's is_steady() (which is documented to return false in force/teach mode) |
| Live telemetry | ur_get_runtime_telemetry / ur_get_robot_voltage / ur_get_robot_current / ur_get_joint_temperatures / ur_get_speed_scaling / ur_get_tcp_force / ur_get_tool_telemetry | Joint currents/voltages/speeds, TCP speed and wrench, tool accelerometer, speed scaling, robot voltage/current, joint temperatures, tool current/voltage, I/O current. These fields are already in the 500 Hz RTDE stream, so reading them is free and needs no reconfiguration. Single-value reads and the one-shot ur_get_runtime_telemetry summary coexist: poll a single value when that is all you need, use the summary for diagnosis |
| Conveyor | ur_get_conveyor / ur_set_conveyor_tick / ur_set_conveyor_tracking | Conveyor tick read / set, and starting/stopping linear or circular tracking |
| Motion | ur_movej / ur_movel / ur_movep / ur_movec / ur_servoj / ur_move_optimized / ur_move_x / ur_move_y / ur_move_z / ur_move_tool_x / ur_move_tool_y / ur_move_tool_z | Joint-space / linear / path / genuine circular motion (the old implementation hardcoded movetype='p' internally, so it actually sent movep and discarded the via point; 0.6.0 adds the manual's mode argument: 0 = interpolate orientation, 1 = fixed orientation) / continuous servo / OptiMove (optimovej/optimovel: jerk-limited, smoother, less vibration — ⚠️ its a/v are fractions of capability in (0,1], not rad/s or m/s) / axis-aligned motion — the _move_* tools move along base axes, the _move_tool_* ones along the current tool axes (converted in the worker with a rotation matrix, so it does not depend on URScript pose_trans). The default a/v are the manual's again (movej 1.4 / 1.05, movel 1.2 / 0.25; the old code defaulted movel to v=1 m/s, four times the manual value) |
| Motion planning | ur_motion_version / ur_get_freedrive_status | Set the Motion Version (manual chapter 14) and the jerk gain (0.01-1.0, which only affects jerk-limited profiles: version-2 movej/movel and optimovej/optimovel). Version 2 clamps velocities/accelerations to the hardware limits while planning and shrinks blend radii dynamically instead of skipping the whole move with an "Overlapping Blends" warning as version 1 does. ⚠️ Newer robot models and PolyScope X support only version 2; CB3 has no such setting. ⚠️ Neither setting has a read-back channel, so the tool reports what it set. ur_get_freedrive_status returns how close the current pose is to a singularity during freedrive (0 normal / 1 near / 2 too close — not an on/off flag), which is what tells an operator to pick another path |
| Drawing | ur_draw_circle / ur_draw_square / ur_draw_rectangle / ur_draw_star | Draw a circle / square / rectangle / pentagram. "Completed" is only reported after the script was actually observed running — the old code probed immediately after sending, inevitably saw "not running", and reported success whether or not the script ever executed |
| Script / emergency | ur_send_script / ur_reset_error | Send URScript (execution-verified: sentinels are injected inside the function body / between top-level statements and read back, so "sent" never masquerades as "ran"; with several functions and no visible call site it refuses to verify and returns verified: null) / reset errors |
Every tool except connect takes an ip argument and requires that IP to be connected first.
📚 0.6.0 manual alignment: this release checked signatures, defaults, ranges and deprecated functions against the three official manuals bundled in
ScriptManual/(URSoftware 3.15.4 / PolyScope 5 / PolyScope X); the analysis is indocs/urscript-manual-analysis.md. Three findings changed behaviour: ①movel/movejdefaults are the manual's again; ②ur_force_modestates plainly thatdamping/gain_scalingcannot be read back; ③set_target_payloadis only used when an inertia matrix is supplied (older firmware falls back toset_payload_mass+set_payload_cog).
Read-only 3D digital twin
The plugin also ships a read-only 3D digital twin of the robot, rendered with three.js: the entry is a card in the right sidebar's Start panel, directly below the "Workspace files / New terminal / Browser" cards, and selecting it fills the right sidebar's content area with the live 3D view. The view polls a single state source, so it stays in sync with the live robot's joint poses, tool (TCP) coordinate frame and recent motion trajectory. The twin is strictly read-only — it never sends a command to the robot: it only polls the host's read-only routes and renders what it receives.
Host-side routes (all fenced to loopback callers):
| Route | Description |
|---|---|
GET /dsh-nonead-ur/twin/state[?ip=][&detail=1] | Live pose (joint angles / TCP / model). detail=1 adds the dashboard-side state (safety mode, run state, speed scaling, joint temperatures/currents); the pose channel keeps polling independently and a failing detail query never takes it down |
GET /dsh-nonead-ur/twin/asset?model= | GLB mesh, with a content-hash ETag + immutable and If-None-Match → 304 support |
GET /dsh-nonead-ur/twin/models | The model list actually present locally |
Failure responses carry a machine-readable code (no_robot / robot_not_connected / ambiguous_robot / worker_unavailable / robot_error) and echo the resolved ip, so the UI can say which of four very different failures happened (it used to render one "not connected" line for all of them).
Install into a DSH profile
Option 1 — install as a bundle (manual)
-
Add the plugin as a dependency in your target profile's
package.json:// C:\Users\\.dsh\profiles\web\package.json { "dependencies": { "dsh-nonead-universal-robots": "^0.6.1" }, "dsh": { "profile": { "bundles": [ // add the plugin here (later order is fine) "...", "dsh-nonead-universal-robots" ] } } } -
Install dependencies via
dsh plugin(equivalent to running pnpm inside the profile directory):dsh plugin --profile web install # or cd "$DSH_HOME/profiles/web" && pnpm install -
Restart DSH. The plugin's
cordis.patch.ymlautomatically inserts the plugin line into the config tree, and the tools appear in the model's tool list asur_*.
Option 2 — local path (development)
Add the repository path to the profile dependency (pnpm supports file:) to iterate in this repository:
"dependencies": {
"dsh-nonead-universal-robots": "file:D:/MyProgram/GitLab/dsh-Nonead-Universal-Robots/dsh-nonead-universal-robots"
}
Python runtime
The plugin needs a usable Python and a few dependencies (numpy, paramiko) to start the worker. Point it at an interpreter with the pythonBin config:
pip install -r requirements.txt
pythonBin (default python), commandTimeoutMs (motion/script timeout, default 60000), and connectTimeoutMs (first-connect timeout, default 30000) in cordis.patch.yml can all be overridden per deployment. If python is not on PATH, use an absolute path, e.g. "C:\\Python312\\python.exe" or "/usr/bin/python3".
⚠️ The dependencies must be installed for the interpreter the worker actually uses (a real trap)
lib/worker.jspointsPYTHONPATHat the plugin'spython/directory, and CPython importssitecustomize.pyfrom there at startup if it exists. This repository once carried a machine-localpython/sitecustomize.pythat hardcoded a user-levelsite-packagespath intosys.path— so it worked on that one machine, while on any other machine, or from a published install (the file is not in thefileslist),import numpyfails outright and every tool call dies. The correct setup is to install the dependencies for the interpreterpythonBinpoints at (or point it at a venv).ur_ping/npm run test:pythonis the self-check for that chain, andnpm run verify:hostvalidates tool registration through the host's real schema DSL (it should report 83/83).