NoneadChina/dsh-nonead-universal-robots ↗★ 5

dsh-nonead-universal-robots

一个让 DSH 用自然语言直接控制 Universal Robots(UR)机械臂的插件,由拓德科技(Nonead)基于自研的 nUR MCP Server 同源逻辑开发。 适合需要使用 DSH 智能体工具直接调试和控制 UR 机械臂的用户。

패키지
dsh-nonead-universal-robots
호환성
미검증
Harness peer 범위
^0.1.2-rc.1 || ^0.1.7-rc.2
Cordis peer 범위
^4.0.2
버전
0.6.1
라이선스
NOASSERTION
최근 업데이트
2026. 9. 27.

설치

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:NoneadChina/dsh-nonead-universal-robots

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 bash tool. 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, plus ur_motion_version, ur_set_payload and ur_set_gravity — pause and wait for human confirmation before being sent to the robot. In an interactive deployment (approval policy ask) a confirmation dialog is shown in the UI; a call made without an approval service, without an agent, or answered with anything other than allowed-once fails 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. Set requireApprovalForMotion: false to disable this gate deliberately. test/approval-gate.test.mjs drives the real tool registry and proves all of the above, including that an extra op argument cannot re-route a call.


Feature overview

83 tools (ur_*) in total.

CategoryTool(s) (ur_*)Description
Connectionur_connect / ur_disconnectConnect / disconnect a UR robot by IP
Statusur_get_statusOne-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
Poseur_get_tcp_pose / ur_get_joint_poseRead current TCP pose / joint angles
Targetsur_get_target_valuesRead 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 infour_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_modeModel (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
Programsur_get_program_state / ur_load_program / ur_run_program / ur_stop_program / ur_pause_program / ur_list_programsProgram 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
Registersur_get_int_register / ur_get_double_register / ur_get_bit_registerRead Int / Double / Bool registers
Healthur_pingCheck worker/Python/URBasic readiness without a robot
I/Our_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_inDigital 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 configur_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_communicationTool 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 / safetyur_set_freedrive / ur_set_teach_mode / ur_power_on / ur_power_off / ur_brake_release / ur_unlock_protective_stop / ur_shutdownFreedrive / 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 controlur_force_mode / ur_end_force_mode / ur_force_mode_settingsForce 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 controlur_speedj / ur_speedl / ur_stopj / ur_stopl / ur_wait_steadyJoint/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 telemetryur_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_telemetryJoint 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
Conveyorur_get_conveyor / ur_set_conveyor_tick / ur_set_conveyor_trackingConveyor tick read / set, and starting/stopping linear or circular tracking
Motionur_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_zJoint-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 planningur_motion_version / ur_get_freedrive_statusSet 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
Drawingur_draw_circle / ur_draw_square / ur_draw_rectangle / ur_draw_starDraw 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 / emergencyur_send_script / ur_reset_errorSend 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 in docs/urscript-manual-analysis.md. Three findings changed behaviour: ① movel/movej defaults are the manual's again; ② ur_force_mode states plainly that damping/gain_scaling cannot be read back; ③ set_target_payload is only used when an inertia matrix is supplied (older firmware falls back to set_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):

RouteDescription
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/modelsThe 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)

  1. 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"
          ]
        }
      }
    }
    
  2. 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
    
  3. Restart DSH. The plugin's cordis.patch.yml automatically inserts the plugin line into the config tree, and the tools appear in the model's tool list as ur_*.

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.js points PYTHONPATH at the plugin's python/ directory, and CPython imports sitecustomize.py from there at startup if it exists. This repository once carried a machine-local python/sitecustomize.py that hardcoded a user-level site-packages path into sys.path — so it worked on that one machine, while on any other machine, or from a published install (the file is not in the files list), import numpy fails outright and every tool call dies. The correct setup is to install the dependencies for the interpreter pythonBin points at (or point it at a venv). ur_ping / npm run test:python is the self-check for that chain, and npm run verify:host validates tool registration through the host's real schema DSL (it should report 83/83).