PRJEDU_robotarm — a robot arm in a cell¶
Every file of this example — the XML, the scripts, the page — is in
PRJEDU_robotarm on GitHub.
Run it with run.bat (Windows) or ./run.sh (Linux) from that folder.
The laboratory of a robotics or automation course, in one engine: a six-axis collaborative robot arm that picks parts and puts them down, and the cell controller around it — the part that a PLC or a line controller does in a real factory.
run.bat
Then open http://127.0.0.1:8098/ and press Brake release, Load pick_place, Play, and switch the feeder on.
On Linux the same example starts with ./run.sh (the engine in ../bin/linux-x64; see
"On Linux" in the top README).
| Document | Role | What it is |
|---|---|---|
ROB/S00 |
the robot, simulated | a six-axis arm and its controller, built after a Universal Robots UR5e. It speaks the two interfaces a cell usually uses on a real one: the dashboard server (text lines, TCP 29999) and the Modbus TCP server (registers at UR's addresses) |
CEL/S00 |
the cell controller | a dashboard client and a Modbus client to the robot, the part handshake, a feeder, the rules an operator must not break — and the page |
The cell never reads the robot's document. Everything it knows comes over the same two connections it would use on a real robot.
The robot¶
The arm. Six joints with the UR5e's Denavit-Hartenberg parameters.
The tool position is worked out from the joint angles —
forward kinematics, main.fk in db_ROB.js: six 4×4 transforms
multiplied, and the orientation turned into a rotation vector the way UR
reports it. Motion is joint motion (movej): every joint goes straight
to its target angle, all arriving together; the joint with furthest to go
sets the pace and ramps up and down (60 °/s at most, 120 °/s²).
The programs are data. PROGRAMS holds them as JSON; main.run
steps through them:
| step | does |
|---|---|
movej |
move to a waypoint (joint angles, degrees, in WAYPOINTS) |
wait_reg / set_reg |
wait for a register to hold a value / write one |
grip |
close or open the gripper |
wait |
wait so many seconds |
count |
add one to a register |
goto |
jump to a step |
The waypoints were found once, offline, by inverse kinematics: the joint
angles that put the tool at the pick point (0.40, −0.30, 0.07 m) and the
place point (0.40, 0.30, 0.09 m), pointing straight down. The base goes to
201.4° rather than −158.6°: a movej goes the short way round only if the
numbers say so.
The dashboard — one command, one reply, both text lines:
| command | reply |
|---|---|
power on / power off |
Powering on / Powering off |
brake release |
Brake releasing |
load pick_place.urp |
Loading program: /programs/pick_place.urp |
play / pause / stop |
Starting program / Pausing program / Stopped |
robotmode |
Robotmode: RUNNING (POWER_OFF, POWER_ON, IDLE, RUNNING) |
safetystatus |
Safetystatus: NORMAL (PROTECTIVE_STOP, SAFEGUARD_STOP) |
programState |
PLAYING pick_place.urp |
unlock protective stop |
Protective stop releasing |
These are the replies UR's own client library expects. A new connection is
greeted first — Connected: Universal Robots Dashboard Server — and a
client that takes that line for the answer to its first command is off by
one from then on. Power and brakes take time here:
power on answers at once, but the mode goes POWER_ON and only 2 s later
IDLE; brake release takes another 1.5 s to RUNNING.
The registers (holding registers; a real controller serves them on port 502, this one on 1504):
| register | ||
|---|---|---|
| 128..135 | general purpose | the cell writes 128 (part ready) and 132 (speed %); the robot program writes 129 (parts placed), 130 (gripper), 131 (part taken), 133 (program step) |
| 270..275 | joint positions | mrad, base … wrist 3 |
| 280..285 | joint speeds | mrad/s |
| 400..405 | tool position | x y z in 0.1 mm, rx ry rz in mrad |
Negative values are 16-bit two's complement. The cell turns them into
degrees and millimetres by configuration — val_cnv_dlt="0.0572958" on a
joint, "0.1" on a coordinate — not by script.
Safety. Two things in the world stop the arm, and the page has a button for each:
- Walk into the arm — a collision:
PROTECTIVE_STOP. The program pauses.unlock protective stopis refused for 5 s ("Cannot unlock protective stop until 5s after occurrence. Always inspect cause of protective stop before unlocking"), thenplayresumes where it stopped. - Open the guard door —
SAFEGUARD_STOPwhile it is open. The program pauses; close the door andplayagain.
The cell¶
The handshake. The robot program and the cell agree through general purpose registers, the way a PLC and a robot program do:
cell 128 = 1 a part is ready at the pick position
robot 131 = 1 I have taken it
cell 128 = 0 seen
robot 131 = 0 seen - and off to put it down
Each side acts only on the other's register, so a handshake cut in the middle (a stop, a reconnect) is never ambiguous. The cell counts a part as gone on the edge of 131 (0 → 1): its level lasts several polls and each would count it again.
The feeder brings a part every FEED_S (6) seconds; up to four wait
on the infeed. The cell times every part put down (CYCLE_S) and keeps
parts a minute over the last five (RATE).
The rules live in the cell, not in the page. play only when the
robot is RUNNING and its safety NORMAL; power off not while a
program plays; commands the dashboard does not have are refused before
they are sent. The page may write CMD, FEED and SPEED only
(xdb_acl); the robot's own safety is the robot's — the cell shows it
(SAFETY_CODE, an alarm) and never clears it by itself.
The dashboard queue. One command at a time, one reply each: commands
wait in QUEUE, BUSY is the one waiting for its reply (2 s at most).
Once a second the cell asks robotmode, safetystatus and
programState; an operator's pause or stop goes to the front.
The page¶
- the arm from above and from the side, drawn from the six joint angles by the same forward kinematics, with the tool position the robot reports as a dot (they should agree) and a trail of where the tool has been
- the robot's buttons, the cell's feeder and speed, the handshake registers as lamps, the world's two buttons
- the joints with their speeds, the tool position, and the dashboard conversation: every operator command and the robot's reply
A real robot, or URSim¶
Not tested against a real robot or URSim for this example; the protocol details are UR's published ones (see What it measured for what was).
- URSim, UR's free simulator (a Docker image,
universalrobots/ursim_e-series), or a real UR (CB3 or e-Series). Expose ports 29999 and 502. - On the robot, write a program (
pick_place.urp) that does whatPROGRAMSdoes here:read_port_register(128)to wait for a part,write_port_register(131, 1)when it has it, and so on — the registers are the contract, not the program. - In
db/db_CEL.xml:CN_DASH→con_host= the robot's address;<dev id="ROBOT">→con_hostandcon_port="502". - In
db/AyXDB.xml: switch the simulated robot off —<doc id="ROB/S00" sta="0x0000" ...>.
A real robot has its own safety system; nothing here replaces it.
What it measured¶
On a Windows 11 laptop, engine 4.3.4.464, and on Linux (WSL, Ubuntu 22.04):
| time per part, speed 100 % | 8.72 s on average (8.32 … 8.98, nine parts) — 6.9 parts a minute |
| time per part, speed 50 % | 11.63 s (six parts) — 5.2 a minute. Not twice as long: the gripper's waits and the handshake do not slow down, and short moves spend most of their time accelerating |
| the feeder (a part every 6 s) against the robot (one every 8.7 s) | the infeed fills up to its four and stays there: the robot is the bottleneck |
| the tool at the waypoints, as the robot reports it | (400.1, −299.6, 69.9) mm at the pick point (target 400, −300, 70); the lowest the tool went in 170 s: 69.9 mm |
| the dashboard | 492 status questions in 170 s, 0 replies missing; a question takes about 80 ms from sending to its reply |
| protective stop | unlock 2 s after the stop refused (the reply above); unlock after 5 s accepted, play resumed at the step it had stopped at |
| safeguard stop | play with the door open refused by the cell ("play needs safety NORMAL"); door closed, play, the program went on |
| the engine | 16 % of one core, 31 MB — the robot's 20 steps a second (forward kinematics, the program, 24 registers) and the cell's four polls every 100 ms |
| FREE edition | the same run with no licence: six connections, all working |
| Linux | the same startup, the same 8.7 s per part, 0 missing replies |
Five things the measuring found, all fixed before this was written — and worth knowing for any script you write:
Time.GetTimeMs()is an unsigned 64-bit integer.now - twith atanother thread wrote a moment afternowwas read is not −5 but 18 446 744 073 709 551 611. The cell's reply timeout fired 15 ms after a question, and every answer after it went to the wrong question. Both documents now read the clock as a float:0.0 + Time.GetTimeMs().9003 / 60000.0is 0 when 9003 is an integer: the left operand decides the arithmetic. Parts a minute stayed at 0 until the subtraction was made a float too.val_fncdoes not run on an element whoseval_typhas no function bit (0x1000,0x2000or0x3000):0x0401stores the page's command and runs nothing,0x1401runsmain.on_cmd.PRO.outsends nothing for a value that arrived from a poll — so the cell does not write register 128 from the poll's ownpro_fncchain but from its timer — and the address it writes is the block'spro_adoplus the<io>'s (128 + 0, not 128 + 128).- A local attribute (
val_typ0x4…) refuses a write that came in from another document, without a word. The page writesROB/S00/BUMPthrough the cell's MQTT connection;main.on_bumpthen could not write the stop time into a localPSTOP_MS, and the 5-second rule never held. It is not local now.
Try this¶
| Do | See |
|---|---|
| Play before Brake release | the cell refuses it: the robot is not RUNNING. Ask the robot directly (take the rule out of main.on_cmd) and it answers Failed to execute: play |
| Walk into the arm, then Unlock at once | the robot refuses for 5 s. Why would a robot maker insist on that? |
| the speed slider to 30 % | time per part and parts a minute. Which moves get slower, and which do not? |
the feeder: FEED_S 12 in db_CEL.xml |
now the robot waits for parts: wait_reg 128 holds it above the pick point. What is the best FEED_S for this robot? |
| move the place point | work out new joint angles for another point (the inverse kinematics of a UR5e is a classic exercise), put them in WAYPOINTS, and watch the tool position the robot reports |
| a keep-out plane | lower the pick waypoint until TCP_Z falls below 50 mm: the cell's warning (ALM.onlvl on TCP_Z). Should the cell only warn, or pause the robot? |
| a second program | home.urp is there; write place_stack.urp that puts each part a little higher than the last |
Licence¶
Six connections (two in the robot, four in the cell) and three documents: exactly the FREE edition's limit; it runs without a licence.