@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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -0
  3. package/docs/ARCHITECTURE.md +51 -0
  4. package/examples/01-inject-demo.json +133 -0
  5. package/examples/02-controller-to-robot.json +72 -0
  6. package/examples/03-rosbridge-live.json +54 -0
  7. package/examples/04-controller-and-ros2.json +72 -0
  8. package/examples/05-dashboard2.json +143 -0
  9. package/lib/normalize.js +146 -0
  10. package/lib/robot-runtime.js +144 -0
  11. package/lib/rosbridge.js +210 -0
  12. package/models/ur5e/LICENSE +33 -0
  13. package/models/ur5e/README.md +15 -0
  14. package/models/ur5e/joints.json +12 -0
  15. package/models/ur5e/meshes/base.stl +0 -0
  16. package/models/ur5e/meshes/forearm.stl +0 -0
  17. package/models/ur5e/meshes/shoulder.stl +0 -0
  18. package/models/ur5e/meshes/upperarm.stl +0 -0
  19. package/models/ur5e/meshes/wrist1.stl +0 -0
  20. package/models/ur5e/meshes/wrist2.stl +0 -0
  21. package/models/ur5e/meshes/wrist3.stl +0 -0
  22. package/models/ur5e/ur5e.urdf +138 -0
  23. package/models/ur5e-gripper/LICENSE +33 -0
  24. package/models/ur5e-gripper/README-ur5e.md +15 -0
  25. package/models/ur5e-gripper/README.md +6 -0
  26. package/models/ur5e-gripper/joints.json +13 -0
  27. package/models/ur5e-gripper/meshes/base.stl +0 -0
  28. package/models/ur5e-gripper/meshes/forearm.stl +0 -0
  29. package/models/ur5e-gripper/meshes/shoulder.stl +0 -0
  30. package/models/ur5e-gripper/meshes/upperarm.stl +0 -0
  31. package/models/ur5e-gripper/meshes/wrist1.stl +0 -0
  32. package/models/ur5e-gripper/meshes/wrist2.stl +0 -0
  33. package/models/ur5e-gripper/meshes/wrist3.stl +0 -0
  34. package/models/ur5e-gripper/ur5e_gripper.urdf +182 -0
  35. package/nodes/controller.html +76 -0
  36. package/nodes/controller.js +237 -0
  37. package/nodes/robot.html +109 -0
  38. package/nodes/robot.js +347 -0
  39. package/nodes/ui-robot-arm.html +82 -0
  40. package/nodes/ui-robot-arm.js +25 -0
  41. package/nodes/ui-robot-controller.html +81 -0
  42. package/nodes/ui-robot-controller.js +25 -0
  43. package/package.json +97 -0
  44. package/resources/controller.html +64 -0
  45. package/resources/controller.js +114 -0
  46. package/resources/pendant.html +66 -0
  47. package/resources/pendant.js +129 -0
  48. package/resources/robot-arm-widgets.umd.js +2 -0
  49. package/resources/viewer.html +54 -0
  50. package/resources/viewer.js +195 -0
  51. package/ui/components/UIRobotArm.vue +63 -0
  52. package/ui/components/UIRobotController.vue +62 -0
  53. 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
+ ]