@tteja/node-red-dashboard-2-robot-arm 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +142 -0
- package/docs/ARCHITECTURE.md +51 -0
- package/examples/01-inject-demo.json +133 -0
- package/examples/02-controller-to-robot.json +72 -0
- package/examples/03-rosbridge-live.json +54 -0
- package/examples/04-controller-and-ros2.json +72 -0
- package/examples/05-dashboard2.json +143 -0
- package/lib/normalize.js +146 -0
- package/lib/robot-runtime.js +144 -0
- package/lib/rosbridge.js +210 -0
- package/models/ur5e/LICENSE +33 -0
- package/models/ur5e/README.md +15 -0
- package/models/ur5e/joints.json +12 -0
- package/models/ur5e/meshes/base.stl +0 -0
- package/models/ur5e/meshes/forearm.stl +0 -0
- package/models/ur5e/meshes/shoulder.stl +0 -0
- package/models/ur5e/meshes/upperarm.stl +0 -0
- package/models/ur5e/meshes/wrist1.stl +0 -0
- package/models/ur5e/meshes/wrist2.stl +0 -0
- package/models/ur5e/meshes/wrist3.stl +0 -0
- package/models/ur5e/ur5e.urdf +138 -0
- package/models/ur5e-gripper/LICENSE +33 -0
- package/models/ur5e-gripper/README-ur5e.md +15 -0
- package/models/ur5e-gripper/README.md +6 -0
- package/models/ur5e-gripper/joints.json +13 -0
- package/models/ur5e-gripper/meshes/base.stl +0 -0
- package/models/ur5e-gripper/meshes/forearm.stl +0 -0
- package/models/ur5e-gripper/meshes/shoulder.stl +0 -0
- package/models/ur5e-gripper/meshes/upperarm.stl +0 -0
- package/models/ur5e-gripper/meshes/wrist1.stl +0 -0
- package/models/ur5e-gripper/meshes/wrist2.stl +0 -0
- package/models/ur5e-gripper/meshes/wrist3.stl +0 -0
- package/models/ur5e-gripper/ur5e_gripper.urdf +182 -0
- package/nodes/controller.html +76 -0
- package/nodes/controller.js +237 -0
- package/nodes/robot.html +109 -0
- package/nodes/robot.js +347 -0
- package/nodes/ui-robot-arm.html +82 -0
- package/nodes/ui-robot-arm.js +25 -0
- package/nodes/ui-robot-controller.html +81 -0
- package/nodes/ui-robot-controller.js +25 -0
- package/package.json +97 -0
- package/resources/controller.html +64 -0
- package/resources/controller.js +114 -0
- package/resources/pendant.html +66 -0
- package/resources/pendant.js +129 -0
- package/resources/robot-arm-widgets.umd.js +2 -0
- package/resources/viewer.html +54 -0
- package/resources/viewer.js +195 -0
- package/ui/components/UIRobotArm.vue +63 -0
- package/ui/components/UIRobotController.vue +62 -0
- package/ui/index.js +2 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Teja
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# @tteja/node-red-dashboard-2-robot-arm
|
|
2
|
+
|
|
3
|
+
A Node-RED robot-arm node for a **UR5e**: a 3D view of the real URDF meshes, a teach pendant, and optional ROS 2 joint feedback. On a Dashboard 2 page the arm and the pendant sit side by side. You can also drive the arm from Inject or Function nodes without a dashboard.
|
|
4
|
+
|
|
5
|
+
Palette name: `@tteja/node-red-dashboard-2-robot-arm`. The `@tteja` scope is the name the Flow Library asks for on new packages. The `dashboard-2` part is required so an existing Dashboard 2 install will show the arm and pendant on the page. Guide: https://github.com/t-teja/node-red-contrib-robot-arm#readme
|
|
6
|
+
|
|
7
|
+
## Add it in Node-RED
|
|
8
|
+
|
|
9
|
+
1. Open the editor menu → **Manage palette** → **Install**.
|
|
10
|
+
2. Search for `@tteja/node-red-dashboard-2-robot-arm` and install it.
|
|
11
|
+
3. For the on-page 3D view and pendant, also install **@flowfuse/node-red-dashboard** (Dashboard 2) if it is not already there.
|
|
12
|
+
4. Restart Node-RED if the new nodes do not appear. They show up under **robotics**, and the two dashboard widgets under **dashboard**.
|
|
13
|
+
|
|
14
|
+
The `robot-arm` and `robot-arm-controller` nodes work without Dashboard 2. The `ui-ur5e-arm` and `ui-ur5e-controller` widgets need it.
|
|
15
|
+
|
|
16
|
+
## The four nodes
|
|
17
|
+
|
|
18
|
+
| Node | What you use it for |
|
|
19
|
+
|------|---------------------|
|
|
20
|
+
| **robot-arm** | The arm. Holds the UR5e model, accepts joint commands, and publishes the current joint state. |
|
|
21
|
+
| **robot-arm-controller** | A teach pendant on its own page. Wire its output into **robot-arm**. |
|
|
22
|
+
| **ui-ur5e-arm** | Dashboard 2 widget. Shows the 3D arm for a **robot-arm** node you pick. |
|
|
23
|
+
| **ui-ur5e-controller** | Dashboard 2 widget. Pendant for that same **robot-arm** node. No extra wire is required. |
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Inject / Function / rosbridge Dashboard 2 page
|
|
27
|
+
│ ┌─ ui-ur5e-arm ────────┐
|
|
28
|
+
▼ │ 3D view │
|
|
29
|
+
┌──────────────┐ ├─ ui-ur5e-controller ─┤
|
|
30
|
+
│ robot-arm │◀── pendant ───────│ joint sliders │
|
|
31
|
+
└──────────────┘ └───────────────────────┘
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
On **robot-arm**, choose the model:
|
|
35
|
+
|
|
36
|
+
| Preset | What you get |
|
|
37
|
+
|--------|----------------|
|
|
38
|
+
| **ur5e** | UR5e arm, official-style meshes. This is the default. |
|
|
39
|
+
| **ur5e-gripper** | The same arm plus a simple two-finger gripper on the tool. |
|
|
40
|
+
|
|
41
|
+
## Use it on a dashboard
|
|
42
|
+
|
|
43
|
+
This is the layout in example **05 Dashboard 2**.
|
|
44
|
+
|
|
45
|
+
1. Import that example (see [Examples](#examples) below).
|
|
46
|
+
2. Deploy.
|
|
47
|
+
3. Open the Dashboard page **Robot Arm**. With a normal Dashboard 2 install that is `http://<your-node-red-host>:1880/dashboard/robot`.
|
|
48
|
+
4. The left panel is the 3D arm. The right panel is the pendant.
|
|
49
|
+
|
|
50
|
+
The base stays in the middle of the 3D panel, and the zoom fits the panel. You can orbit around the base. The pendant sliders, **Home**, and **Freeze** move that same arm.
|
|
51
|
+
|
|
52
|
+
If you already have a dashboard, you do not have to import the whole example. Add a **robot-arm** node, then add **ui-ur5e-arm** and **ui-ur5e-controller** to a page and, in each widget, select that robot-arm node.
|
|
53
|
+
|
|
54
|
+
**Freeze** is a soft stop in the UI. It is not a hardware emergency stop. See [Safety](#safety).
|
|
55
|
+
|
|
56
|
+
## Use it from a flow
|
|
57
|
+
|
|
58
|
+
Drop a **robot-arm** node on a tab and deploy. Anything you send to its input becomes the next joint command, and the output is the joint state after that command.
|
|
59
|
+
|
|
60
|
+
The node status shows a 3D view path, `/robot-arm/view/<robot-node-id>`. Open that on the Node-RED host to see the arm without Dashboard 2.
|
|
61
|
+
|
|
62
|
+
A separate **robot-arm-controller** node is the same pendant on its own page. Wire **robot-arm-controller → robot-arm**, deploy, and open the controller URL shown on that node (`/robot-arm/controller/<id>`). Example **02** is this wiring.
|
|
63
|
+
|
|
64
|
+
## Examples
|
|
65
|
+
|
|
66
|
+
After the package is installed, the examples are in the editor:
|
|
67
|
+
|
|
68
|
+
**Menu → Import → Examples → @tteja/node-red-dashboard-2-robot-arm**
|
|
69
|
+
|
|
70
|
+
Import one, then deploy.
|
|
71
|
+
|
|
72
|
+
| Example | What to do with it |
|
|
73
|
+
|---------|-------------------|
|
|
74
|
+
| **01 Inject demo** | Three Inject nodes: Home, Reach, and Wave. Click one and the arm moves. A debug node shows the joint state coming out of **robot-arm**. |
|
|
75
|
+
| **02 Controller to robot** | Pendant wired into the arm. Open the controller URL on the **robot-arm-controller** node and move the sliders. |
|
|
76
|
+
| **03 Rosbridge live** | **robot-arm** with rosbridge turned on, subscribed to `/joint_states`. Use this when a ROS 2 robot or simulator is publishing joint states. |
|
|
77
|
+
| **04 Controller and ROS 2** | Pendant and rosbridge together. While the pendant is armed, its commands win for about 2 seconds over incoming `/joint_states`. Disarm the pendant to let ROS drive the view. |
|
|
78
|
+
| **05 Dashboard 2** | A Dashboard page named **Robot Arm** with the 3D view and the pendant bound to one **robot-arm** node. Start here if you want the arm on a dashboard. |
|
|
79
|
+
|
|
80
|
+
## Messages
|
|
81
|
+
|
|
82
|
+
Send any of these on the **robot-arm** input. The output is always the canonical object below.
|
|
83
|
+
|
|
84
|
+
Canonical command and state:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"robot": "robot",
|
|
89
|
+
"joints": {
|
|
90
|
+
"shoulder_pan_joint": 0,
|
|
91
|
+
"shoulder_lift_joint": -1.57,
|
|
92
|
+
"elbow_joint": 1.57,
|
|
93
|
+
"wrist_1_joint": -1.57,
|
|
94
|
+
"wrist_2_joint": -1.57,
|
|
95
|
+
"wrist_3_joint": 0
|
|
96
|
+
},
|
|
97
|
+
"unit": "rad",
|
|
98
|
+
"source": "pendant",
|
|
99
|
+
"ts": 1710000000000
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Also accepted: a ROS `JointState` style `{ "name": [...], "position": [...] }`, a flat map of joint name to number, or an array of numbers in the joint order above.
|
|
104
|
+
|
|
105
|
+
### Why slider limits are 6.28 and 3.14
|
|
106
|
+
|
|
107
|
+
The pendant uses **radians**. A full circle is 2 × π ≈ **6.2832 rad = 360°**. Half a circle is π ≈ **3.1416 rad = 180°**.
|
|
108
|
+
|
|
109
|
+
| Joints | Slider range | Meaning |
|
|
110
|
+
|--------|----------------|---------|
|
|
111
|
+
| Base, Shoulder, Wrist 1, Wrist 2, Wrist 3 | −6.2832 … 6.2832 | One full turn either side of zero (±360°) |
|
|
112
|
+
| Elbow | −3.1416 … 3.1416 | Half a turn either side of zero (±180°) |
|
|
113
|
+
|
|
114
|
+
These are the UR5e joint limits from the model, not an arbitrary slider size. **Home** on the pendant returns each joint to its home angle (straight-up style pose: shoulder −1.57 rad, elbow 1.57 rad, and so on). The number beside each slider is the current angle in radians.
|
|
115
|
+
|
|
116
|
+
## ROS 2
|
|
117
|
+
|
|
118
|
+
Optional. On the robot PC or simulator:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
ros2 launch rosbridge_server rosbridge_websocket_launch.xml
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
On the **robot-arm** node, enable rosbridge and set:
|
|
125
|
+
|
|
126
|
+
- URL: `ws://<robot-pc>:9090`
|
|
127
|
+
- Subscribe topic: `/joint_states` (`sensor_msgs/JointState`)
|
|
128
|
+
- Publish topic: optional. When set, commands leaving this node are published there.
|
|
129
|
+
|
|
130
|
+
This node talks to rosbridge over WebSocket. It does not speak DDS itself. Nothing moves unless something publishes `/joint_states` or you send a command from the pendant or a flow.
|
|
131
|
+
|
|
132
|
+
When the pendant is armed, its updates are preferred over rosbridge for about 2 seconds. After that, the latest message wins.
|
|
133
|
+
|
|
134
|
+
## Safety
|
|
135
|
+
|
|
136
|
+
**Not SIL-rated.** This is a viewer and a soft teach pendant. **Freeze** only stops commands inside this UI. Do not use it as the emergency stop or safety controller for a real arm.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT © 2026 Teja.
|
|
141
|
+
|
|
142
|
+
The UR5e meshes are BSD-3-Clause, Copyright Universal Robots A/S, from [UniversalRobots/Universal_Robots_ROS2_Description](https://github.com/UniversalRobots/Universal_Robots_ROS2_Description). See `models/ur5e/LICENSE`. The gripper in the `ur5e-gripper` preset is a simple box model, not a Robotiq CAD model.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
## Palette nodes
|
|
4
|
+
|
|
5
|
+
1. **`robot-arm`** — arm model, normalize, rosbridge, HTTP view + pendant API
|
|
6
|
+
2. **`robot-arm-controller`** — standalone teach pendant (HTTP) for non-Dashboard flows
|
|
7
|
+
3. **`ui-ur5e-arm`** — Dashboard 2 3D view (iframe of `/robot-arm/view/:id`)
|
|
8
|
+
4. **`ui-ur5e-controller`** — Dashboard 2 pendant (iframe of `/robot-arm/pendant/:id`)
|
|
9
|
+
|
|
10
|
+
npm package name is `@tteja/node-red-dashboard-2-robot-arm`. The scope satisfies the Flow Library naming rule for packages published after 2021, and the name still contains `node-red-dashboard-2-` so an existing Dashboard 2 install discovers the widgets. The GitHub repo stays `t-teja/node-red-contrib-robot-arm`.
|
|
11
|
+
|
|
12
|
+
## Data flow
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Function / Inject / rosbridge
|
|
16
|
+
│
|
|
17
|
+
▼
|
|
18
|
+
┌──────────────┐ SSE/HTTP ┌─────────────┐
|
|
19
|
+
│ robot-arm │──────────────────▶│ viewer.js │
|
|
20
|
+
│ RobotRuntime │ │ URDF+STL │
|
|
21
|
+
│ RosbridgeClient └─────────────┘
|
|
22
|
+
└──────▲───────┘
|
|
23
|
+
│ POST /api/:id/joints
|
|
24
|
+
┌──────┴───────┐ ┌─────────────┐
|
|
25
|
+
│ pendant │ │ D2 widgets │
|
|
26
|
+
│ (or ctrl) │ │ (iframes) │
|
|
27
|
+
└──────────────┘ └─────────────┘
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Models
|
|
31
|
+
|
|
32
|
+
- `models/ur5e/` — UR5e URDF + `meshes/*.stl` (BSD-3-Clause)
|
|
33
|
+
- `models/ur5e-gripper/` — same meshes + primitive gripper on `tool0`
|
|
34
|
+
|
|
35
|
+
HTTP serves nested paths: `/robot-arm/models/:preset/*` (e.g. `meshes/base.stl`).
|
|
36
|
+
|
|
37
|
+
## Libraries
|
|
38
|
+
|
|
39
|
+
| File | Role |
|
|
40
|
+
|------|------|
|
|
41
|
+
| `lib/normalize.js` | Payload → canonical joints |
|
|
42
|
+
| `lib/rosbridge.js` | WS client: subscribe / advertise / publish |
|
|
43
|
+
| `lib/robot-runtime.js` | State + applyCommand + publish hook (unit-tested) |
|
|
44
|
+
|
|
45
|
+
## Dashboard 2
|
|
46
|
+
|
|
47
|
+
Built UMD: `resources/robot-arm-widgets.umd.js` (committed). Widgets bind to a `robot-arm` node id via config dropdown.
|
|
48
|
+
|
|
49
|
+
## Safety
|
|
50
|
+
|
|
51
|
+
NOT SIL. Soft UI freeze only.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "flow01",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "01 Inject demo",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "Inject Home / Reach / Wave into robot node"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "robot01",
|
|
11
|
+
"type": "robot-arm",
|
|
12
|
+
"z": "flow01",
|
|
13
|
+
"name": "arm",
|
|
14
|
+
"robotTag": "robot",
|
|
15
|
+
"modelPreset": "ur5e",
|
|
16
|
+
"urdfPath": "",
|
|
17
|
+
"unit": "rad",
|
|
18
|
+
"rosbridgeEnable": false,
|
|
19
|
+
"rosbridgeUrl": "ws://localhost:9090",
|
|
20
|
+
"jointStatesTopic": "/joint_states",
|
|
21
|
+
"publishTopic": "",
|
|
22
|
+
"x": 520,
|
|
23
|
+
"y": 200,
|
|
24
|
+
"wires": [
|
|
25
|
+
[
|
|
26
|
+
"debug01"
|
|
27
|
+
]
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "debug01",
|
|
32
|
+
"type": "debug",
|
|
33
|
+
"z": "flow01",
|
|
34
|
+
"name": "joints out",
|
|
35
|
+
"active": true,
|
|
36
|
+
"tosidebar": true,
|
|
37
|
+
"console": false,
|
|
38
|
+
"tostatus": false,
|
|
39
|
+
"complete": "payload",
|
|
40
|
+
"x": 720,
|
|
41
|
+
"y": 200,
|
|
42
|
+
"wires": []
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "inj_home",
|
|
46
|
+
"type": "inject",
|
|
47
|
+
"z": "flow01",
|
|
48
|
+
"name": "Home",
|
|
49
|
+
"props": [
|
|
50
|
+
{
|
|
51
|
+
"p": "payload"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"p": "topic",
|
|
55
|
+
"vt": "str"
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"repeat": "",
|
|
59
|
+
"crontab": "",
|
|
60
|
+
"once": false,
|
|
61
|
+
"onceDelay": 0.1,
|
|
62
|
+
"topic": "home",
|
|
63
|
+
"payload": "{\"joints\":{\"shoulder_pan_joint\":0,\"shoulder_lift_joint\":-1.57,\"elbow_joint\":1.57,\"wrist_1_joint\":-1.57,\"wrist_2_joint\":-1.57,\"wrist_3_joint\":0},\"unit\":\"rad\",\"source\":\"inject\"}",
|
|
64
|
+
"payloadType": "json",
|
|
65
|
+
"x": 200,
|
|
66
|
+
"y": 120,
|
|
67
|
+
"wires": [
|
|
68
|
+
[
|
|
69
|
+
"robot01"
|
|
70
|
+
]
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"id": "inj_reach",
|
|
75
|
+
"type": "inject",
|
|
76
|
+
"z": "flow01",
|
|
77
|
+
"name": "Reach",
|
|
78
|
+
"props": [
|
|
79
|
+
{
|
|
80
|
+
"p": "payload"
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"repeat": "",
|
|
84
|
+
"crontab": "",
|
|
85
|
+
"once": false,
|
|
86
|
+
"onceDelay": 0.1,
|
|
87
|
+
"topic": "",
|
|
88
|
+
"payload": "{\"joints\":{\"shoulder_pan_joint\":0.5,\"shoulder_lift_joint\":-1.0,\"elbow_joint\":1.2,\"wrist_1_joint\":-1.4,\"wrist_2_joint\":-1.57,\"wrist_3_joint\":0.3},\"unit\":\"rad\",\"source\":\"inject\"}",
|
|
89
|
+
"payloadType": "json",
|
|
90
|
+
"x": 200,
|
|
91
|
+
"y": 200,
|
|
92
|
+
"wires": [
|
|
93
|
+
[
|
|
94
|
+
"robot01"
|
|
95
|
+
]
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "inj_wave",
|
|
100
|
+
"type": "inject",
|
|
101
|
+
"z": "flow01",
|
|
102
|
+
"name": "Wave",
|
|
103
|
+
"props": [
|
|
104
|
+
{
|
|
105
|
+
"p": "payload"
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
"repeat": "",
|
|
109
|
+
"crontab": "",
|
|
110
|
+
"once": false,
|
|
111
|
+
"onceDelay": 0.1,
|
|
112
|
+
"topic": "",
|
|
113
|
+
"payload": "{\"joints\":{\"shoulder_pan_joint\":-0.6,\"shoulder_lift_joint\":-1.2,\"elbow_joint\":1.4,\"wrist_1_joint\":-1.2,\"wrist_2_joint\":-0.8,\"wrist_3_joint\":1.0},\"unit\":\"rad\",\"source\":\"inject\"}",
|
|
114
|
+
"payloadType": "json",
|
|
115
|
+
"x": 200,
|
|
116
|
+
"y": 280,
|
|
117
|
+
"wires": [
|
|
118
|
+
[
|
|
119
|
+
"robot01"
|
|
120
|
+
]
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"id": "comment01",
|
|
125
|
+
"type": "comment",
|
|
126
|
+
"z": "flow01",
|
|
127
|
+
"name": "After deploy: open node status URL /robot-arm/view/<robot-node-id>",
|
|
128
|
+
"info": "Click Home / Reach / Wave. 3D view updates via SSE.",
|
|
129
|
+
"x": 340,
|
|
130
|
+
"y": 60,
|
|
131
|
+
"wires": []
|
|
132
|
+
}
|
|
133
|
+
]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "flow02",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "02 Controller \u2192 robot",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "Teach pendant wired to robot"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "ctrl02",
|
|
11
|
+
"type": "robot-arm-controller",
|
|
12
|
+
"z": "flow02",
|
|
13
|
+
"name": "pendant",
|
|
14
|
+
"robotTag": "robot",
|
|
15
|
+
"modelPreset": "ur5e",
|
|
16
|
+
"jointList": "",
|
|
17
|
+
"unit": "rad",
|
|
18
|
+
"stepSize": 0.01,
|
|
19
|
+
"x": 240,
|
|
20
|
+
"y": 180,
|
|
21
|
+
"wires": [
|
|
22
|
+
[
|
|
23
|
+
"robot02"
|
|
24
|
+
]
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "robot02",
|
|
29
|
+
"type": "robot-arm",
|
|
30
|
+
"z": "flow02",
|
|
31
|
+
"name": "arm",
|
|
32
|
+
"robotTag": "robot",
|
|
33
|
+
"modelPreset": "ur5e",
|
|
34
|
+
"urdfPath": "",
|
|
35
|
+
"unit": "rad",
|
|
36
|
+
"rosbridgeEnable": false,
|
|
37
|
+
"rosbridgeUrl": "ws://localhost:9090",
|
|
38
|
+
"jointStatesTopic": "/joint_states",
|
|
39
|
+
"publishTopic": "",
|
|
40
|
+
"x": 460,
|
|
41
|
+
"y": 180,
|
|
42
|
+
"wires": [
|
|
43
|
+
[
|
|
44
|
+
"debug02"
|
|
45
|
+
]
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "debug02",
|
|
50
|
+
"type": "debug",
|
|
51
|
+
"z": "flow02",
|
|
52
|
+
"name": "out",
|
|
53
|
+
"active": true,
|
|
54
|
+
"tosidebar": true,
|
|
55
|
+
"console": false,
|
|
56
|
+
"tostatus": false,
|
|
57
|
+
"complete": "payload",
|
|
58
|
+
"x": 650,
|
|
59
|
+
"y": 180,
|
|
60
|
+
"wires": []
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "comment02",
|
|
64
|
+
"type": "comment",
|
|
65
|
+
"z": "flow02",
|
|
66
|
+
"name": "Open /robot-arm/controller/<ctrl-id> and /robot-arm/view/<robot-id>",
|
|
67
|
+
"info": "Wire: controller \u2192 robot. Use sliders, Home, Freeze, Arm/Disarm.",
|
|
68
|
+
"x": 360,
|
|
69
|
+
"y": 80,
|
|
70
|
+
"wires": []
|
|
71
|
+
}
|
|
72
|
+
]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "flow03",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "03 Rosbridge live",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "Robot node with rosbridge enabled. Topic: /joint_states (sensor_msgs/JointState)"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "robot03",
|
|
11
|
+
"type": "robot-arm",
|
|
12
|
+
"z": "flow03",
|
|
13
|
+
"name": "arm-ros",
|
|
14
|
+
"robotTag": "robot",
|
|
15
|
+
"modelPreset": "ur5e",
|
|
16
|
+
"urdfPath": "",
|
|
17
|
+
"unit": "rad",
|
|
18
|
+
"rosbridgeEnable": true,
|
|
19
|
+
"rosbridgeUrl": "ws://localhost:9090",
|
|
20
|
+
"jointStatesTopic": "/joint_states",
|
|
21
|
+
"publishTopic": "",
|
|
22
|
+
"x": 400,
|
|
23
|
+
"y": 180,
|
|
24
|
+
"wires": [
|
|
25
|
+
[
|
|
26
|
+
"debug03"
|
|
27
|
+
]
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "debug03",
|
|
32
|
+
"type": "debug",
|
|
33
|
+
"z": "flow03",
|
|
34
|
+
"name": "from ros",
|
|
35
|
+
"active": true,
|
|
36
|
+
"tosidebar": true,
|
|
37
|
+
"console": false,
|
|
38
|
+
"tostatus": false,
|
|
39
|
+
"complete": "true",
|
|
40
|
+
"x": 600,
|
|
41
|
+
"y": 180,
|
|
42
|
+
"wires": []
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "comment03",
|
|
46
|
+
"type": "comment",
|
|
47
|
+
"z": "flow03",
|
|
48
|
+
"name": "On robot PC: ros2 launch rosbridge_server rosbridge_websocket_launch.xml",
|
|
49
|
+
"info": "Set rosbridge URL to ws://<robot-pc>:9090. Subscribes /joint_states. Status shows ros:connected when live.",
|
|
50
|
+
"x": 400,
|
|
51
|
+
"y": 80,
|
|
52
|
+
"wires": []
|
|
53
|
+
}
|
|
54
|
+
]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "flow04",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "04 Controller + ROS2",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "Both pendant and rosbridge. Pendant+armed briefly preferred over rosbridge (P0 last-writer + source tag)."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "ctrl04",
|
|
11
|
+
"type": "robot-arm-controller",
|
|
12
|
+
"z": "flow04",
|
|
13
|
+
"name": "pendant",
|
|
14
|
+
"robotTag": "robot",
|
|
15
|
+
"modelPreset": "ur5e",
|
|
16
|
+
"jointList": "",
|
|
17
|
+
"unit": "rad",
|
|
18
|
+
"stepSize": 0.01,
|
|
19
|
+
"x": 220,
|
|
20
|
+
"y": 160,
|
|
21
|
+
"wires": [
|
|
22
|
+
[
|
|
23
|
+
"robot04"
|
|
24
|
+
]
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "robot04",
|
|
29
|
+
"type": "robot-arm",
|
|
30
|
+
"z": "flow04",
|
|
31
|
+
"name": "arm",
|
|
32
|
+
"robotTag": "robot",
|
|
33
|
+
"modelPreset": "ur5e",
|
|
34
|
+
"urdfPath": "",
|
|
35
|
+
"unit": "rad",
|
|
36
|
+
"rosbridgeEnable": true,
|
|
37
|
+
"rosbridgeUrl": "ws://localhost:9090",
|
|
38
|
+
"jointStatesTopic": "/joint_states",
|
|
39
|
+
"publishTopic": "/joint_command",
|
|
40
|
+
"x": 440,
|
|
41
|
+
"y": 200,
|
|
42
|
+
"wires": [
|
|
43
|
+
[
|
|
44
|
+
"debug04"
|
|
45
|
+
]
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "debug04",
|
|
50
|
+
"type": "debug",
|
|
51
|
+
"z": "flow04",
|
|
52
|
+
"name": "merged",
|
|
53
|
+
"active": true,
|
|
54
|
+
"tosidebar": true,
|
|
55
|
+
"console": false,
|
|
56
|
+
"tostatus": false,
|
|
57
|
+
"complete": "payload",
|
|
58
|
+
"x": 640,
|
|
59
|
+
"y": 200,
|
|
60
|
+
"wires": []
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "comment04",
|
|
64
|
+
"type": "comment",
|
|
65
|
+
"z": "flow04",
|
|
66
|
+
"name": "Priority: pendant (source=pendant, manualArmed) wins for ~2s over rosbridge",
|
|
67
|
+
"info": "Disarm manual on pendant to let ROS2 JointState drive the view. Soft Freeze is UI-only.",
|
|
68
|
+
"x": 400,
|
|
69
|
+
"y": 80,
|
|
70
|
+
"wires": []
|
|
71
|
+
}
|
|
72
|
+
]
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "flow05",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "05 Dashboard 2",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "ui-base / page / group + ui-ur5e-arm + ui-ur5e-controller bound to one robot-arm node"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "ui_base_05",
|
|
11
|
+
"type": "ui-base",
|
|
12
|
+
"name": "Robot UI",
|
|
13
|
+
"path": "/dashboard",
|
|
14
|
+
"includeClientData": true,
|
|
15
|
+
"acceptsClientData": true,
|
|
16
|
+
"showPathInSidebar": false
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "ui_page_05",
|
|
20
|
+
"type": "ui-page",
|
|
21
|
+
"name": "Robot Arm",
|
|
22
|
+
"ui": "ui_base_05",
|
|
23
|
+
"path": "/robot",
|
|
24
|
+
"icon": "mdi-robot-industrial",
|
|
25
|
+
"layout": "grid",
|
|
26
|
+
"theme": "ui_theme_05",
|
|
27
|
+
"order": 1,
|
|
28
|
+
"className": "",
|
|
29
|
+
"visible": true,
|
|
30
|
+
"disabled": false
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "ui_theme_05",
|
|
34
|
+
"type": "ui-theme",
|
|
35
|
+
"name": "Default",
|
|
36
|
+
"colors": {
|
|
37
|
+
"surface": "#0f1419",
|
|
38
|
+
"primary": "#3D7EA6",
|
|
39
|
+
"bgPage": "#0b1016",
|
|
40
|
+
"groupBg": "#1a2332",
|
|
41
|
+
"groupOutline": "#2a3548"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "ui_group_view_05",
|
|
46
|
+
"type": "ui-group",
|
|
47
|
+
"name": "3D View",
|
|
48
|
+
"page": "ui_page_05",
|
|
49
|
+
"width": 8,
|
|
50
|
+
"height": 1,
|
|
51
|
+
"order": 1,
|
|
52
|
+
"showTitle": true,
|
|
53
|
+
"className": "",
|
|
54
|
+
"visible": true,
|
|
55
|
+
"disabled": false
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "ui_group_ctrl_05",
|
|
59
|
+
"type": "ui-group",
|
|
60
|
+
"name": "Pendant",
|
|
61
|
+
"page": "ui_page_05",
|
|
62
|
+
"width": 4,
|
|
63
|
+
"height": 1,
|
|
64
|
+
"order": 2,
|
|
65
|
+
"showTitle": true,
|
|
66
|
+
"className": "",
|
|
67
|
+
"visible": true,
|
|
68
|
+
"disabled": false
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "robot05",
|
|
72
|
+
"type": "robot-arm",
|
|
73
|
+
"z": "flow05",
|
|
74
|
+
"name": "arm",
|
|
75
|
+
"robotTag": "robot",
|
|
76
|
+
"modelPreset": "ur5e",
|
|
77
|
+
"urdfPath": "",
|
|
78
|
+
"unit": "rad",
|
|
79
|
+
"rosbridgeEnable": false,
|
|
80
|
+
"rosbridgeUrl": "ws://localhost:9090",
|
|
81
|
+
"jointStatesTopic": "/joint_states",
|
|
82
|
+
"publishTopic": "",
|
|
83
|
+
"x": 320,
|
|
84
|
+
"y": 200,
|
|
85
|
+
"wires": [
|
|
86
|
+
[
|
|
87
|
+
"debug05"
|
|
88
|
+
]
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "debug05",
|
|
93
|
+
"type": "debug",
|
|
94
|
+
"z": "flow05",
|
|
95
|
+
"name": "joints",
|
|
96
|
+
"active": true,
|
|
97
|
+
"tosidebar": true,
|
|
98
|
+
"console": false,
|
|
99
|
+
"tostatus": false,
|
|
100
|
+
"complete": "payload",
|
|
101
|
+
"x": 500,
|
|
102
|
+
"y": 200,
|
|
103
|
+
"wires": []
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "ui_arm_05",
|
|
107
|
+
"type": "ui-ur5e-arm",
|
|
108
|
+
"z": "flow05",
|
|
109
|
+
"name": "view",
|
|
110
|
+
"group": "ui_group_view_05",
|
|
111
|
+
"order": 1,
|
|
112
|
+
"width": 8,
|
|
113
|
+
"height": 7,
|
|
114
|
+
"robot": "robot05",
|
|
115
|
+
"x": 320,
|
|
116
|
+
"y": 300,
|
|
117
|
+
"wires": []
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"id": "ui_ctrl_05",
|
|
121
|
+
"type": "ui-ur5e-controller",
|
|
122
|
+
"z": "flow05",
|
|
123
|
+
"name": "pendant",
|
|
124
|
+
"group": "ui_group_ctrl_05",
|
|
125
|
+
"order": 1,
|
|
126
|
+
"width": 4,
|
|
127
|
+
"height": 7,
|
|
128
|
+
"robot": "robot05",
|
|
129
|
+
"x": 500,
|
|
130
|
+
"y": 300,
|
|
131
|
+
"wires": []
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"id": "comment05",
|
|
135
|
+
"type": "comment",
|
|
136
|
+
"z": "flow05",
|
|
137
|
+
"name": "Requires @flowfuse/node-red-dashboard. Open Dashboard page Robot Arm \u2014 view + pendant share robot05.",
|
|
138
|
+
"info": "Both widgets bind to the same robot node. Standalone /robot-arm/view and /robot-arm/pendant still work.",
|
|
139
|
+
"x": 420,
|
|
140
|
+
"y": 80,
|
|
141
|
+
"wires": []
|
|
142
|
+
}
|
|
143
|
+
]
|