Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM python:3

# Install python development requirements
COPY requirements-dev.txt /tmp/requirements-dev.txt
RUN pip install --no-cache-dir -r /tmp/requirements-dev.txt

# Install maven and clean up
RUN apt-get update \
&& apt-get install -y maven \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

# set the working directory
WORKDIR /workspace

# Add normal user. USER_UID, and USER_GID are passed from local environment by devcontainer.json.
# Otherwise all files are created with root privileges.
ARG USER_UID
ARG USER_GID

RUN groupadd --gid $USER_GID devuser \
&& useradd --uid $USER_UID --gid $USER_GID --create-home --shell /bin/bash devuser

USER devuser
23 changes: 23 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "Python 3 & Maven Dev Container",
"build": {
"dockerfile": "Dockerfile",
"args": {
"USER_UID": "${localEnv:UID:1000}",
"USER_GID": "${localEnv:GID:1000}"
}
},
"customizations": {
"vscode": {
"extensions": [
"ms-python.python"
],
"settings": {
"python.formatting.provider": "black"
}
}
},
"runArgs": [
"--add-host=controller:host-gateway"
]
}
1 change: 1 addition & 0 deletions .devcontainer/requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
black
67 changes: 47 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# RTDE client library - Python
Library implements API for Universal Robots RTDE realtime interface.

Full RTDE description is available on [Universal Robots support site](https://www.universal-robots.com/support/)
Full RTDE description is available on [Universal Robots documentation site](https://docs.universal-robots.com/tutorials/communication-protocol-tutorials/rtde-guide.html)
# Project structure
## rtde
RTDE core library
Expand All @@ -22,18 +22,24 @@ Copy rtde_control_loop.urp to the robot. Start python script before starting pro
- example_plotting.py - example for using csv_reader, and plotting selected data.

### Running examples
It's recommended to run examples in [virtual environment](https://docs.python.org/3/library/venv.html).
Some require additional libraries.
```
It's recommended to run examples in [virtual environment](https://docs.python.org/3/library/venv.html) or [devcontainer](#using-devcontainer).
```bash
# Example for recording realtime data from the robot
# NOTE: RTDE interface has to be enabled in the robot security settings
cd examples
python record.py -h
python record.py --host 192.168.0.1 --frequency 10
```
# Using robot simulator in Docker
RTDE can connect from host system to controller running in Docker
# Using robot simulator in a Docker
RTDE can connect from host system or [devcontainer](#using-devcontainer) to controller running in a Docker
when RTDE port 30004 is forwarded.
1. Get latest ursim docker image: docker pull universalrobots/ursim_e-series
2. Run docker container: docker run --rm -dit -p 30004:30004 -p 5900:5900 -p 6080:6080 universalrobots/ursim_e-series
3. open vnc client in browser, and confirm safet: http://localhost:6080/vnc.html?host=docker_ip&port=6080
```bash
# 1. Get latest ursim docker image
docker pull universalrobots/ursim_e-series
# 2. Run docker container:
docker run --rm -dit -p 30004:30004 -p 5900:5900 -p 6080:6080 universalrobots/ursim_e-series
# 3. open vnc client in browser, and confirm safety: http://localhost:6080/vnc.html?host=docker_ip&port=6080
```

More information about ursim docker image is available on [Dockerhub](https://hub.docker.com/r/universalrobots/ursim_e-series)

Expand All @@ -49,43 +55,64 @@ when RTDE port 30004 is forwarded.
Leave host, and guest IP fields blank.

# Using rtde library
Copy rtde folder python project
Copy rtde folder to python project or install with
```bash
pip install .
# or use pre-built package from github
pip install ./rtde-<version>-release.zip
pip install https://github.com/UniversalRobots/RTDE_Python_Client_Library/releases/download/[version]/rtde-[version]-release.zip
```

Library is compatible with Python 2.7+, and Python 3.6+

# Build release package
```
```bash
mvn package
```
## Using with virtual environment
## Using pre-built package with virtual environment
Create virtual environment, and install wheel package

### Linux & MacOS
```
```bash
python -m venv venv
source venv/bin/activate
pip install wheel
```
Install rtde package
```
# Install pre-built rtde package
pip install target/rtde-<version>-release.zip
```

### Windows PowerShell
If Python3 is not installed, then just run python3 from powershell. Microsoft store will launch the installation.

Permission to run scripts in console is needed to activate virtual envrionment.
```
```PowerShell
set-executionpolicy -Scope CurrentUser -ExecutionPolicy Unrestricted
python -m venv venv
venv/Scripts/Activate.ps1
pip install wheel
```
Install rtde package
```
# Install pre-built rtde package
pip install target/rtde-<version>-release.zip
```

## Using devcontainer
Open project in VSCode and select to "reopen in devcontainer".
Execute build command from terminal

Running record.py against simulator:
```bash
# first start simulator exposing RTDE port 30004
# docker run --rm -dit -p 30004:30004 -p 5900:5900 -p 6080:6080 universalrobots/ursim_e-series

# in devcontainer terminal type
cd examples
./record.py --host controller --frequency 10 --verbose
```

# Contributor guidelines
Code is formatted with [black](https://github.com/psf/black).
Run code formatter before submitting pull request.

```bash
# open project in devcontainer
python -m black .
```
10 changes: 1 addition & 9 deletions examples/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,6 @@
help="data output file to write to (robot_data.csv)",
)
parser.add_argument("--verbose", help="increase output verbosity", action="store_true")
parser.add_argument(
"--buffered",
help="Use buffered receive which doesn't skip data",
action="store_true",
)
parser.add_argument(
"--binary", help="save the data in binary format", action="store_true"
)
Expand Down Expand Up @@ -114,10 +109,7 @@
if args.samples > 0 and i >= args.samples:
keep_running = False
try:
if args.buffered:
state = con.receive_buffered(args.binary)
else:
state = con.receive(args.binary)
state = con.receive_buffered(args.binary)
if state is not None:
writer.writerow(state)
i += 1
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.ur.rtde.client</groupId>
<artifactId>rtde</artifactId>
<version>2.7.2</version>
<version>2.7.12</version>
<packaging>pom</packaging>

<properties>
Expand Down
14 changes: 12 additions & 2 deletions rtde/csv_binary_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,12 @@ def writeheader(self):
headerStr += self.__header_names[i]

headerStr += "\n"
self.__file.write(struct.pack(str(len(headerStr)) + "s", headerStr if sys.version_info[0] < 3 else headerStr.encode("utf-8")))
self.__file.write(
struct.pack(
str(len(headerStr)) + "s",
headerStr if sys.version_info[0] < 3 else headerStr.encode("utf-8"),
)
)

# Header types
typeStr = str("")
Expand All @@ -119,7 +124,12 @@ def writeheader(self):
typeStr += self.getType(self.__types[i])

typeStr += "\n"
self.__file.write(struct.pack(str(len(typeStr)) + "s", typeStr if sys.version_info[0] < 3 else typeStr.encode("utf-8")))
self.__file.write(
struct.pack(
str(len(typeStr)) + "s",
typeStr if sys.version_info[0] < 3 else typeStr.encode("utf-8"),
)
)

def packToBinary(self, vtype, value):
print(vtype)
Expand Down
46 changes: 39 additions & 7 deletions rtde/rtde.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def __init__(self, hostname, port=30004):
self.__input_config = {}
self.__skipped_package_count = 0
self.__protocolVersion = RTDE_PROTOCOL_VERSION_1
self.__warning_counter = {}

def connect(self):
if self.__sock:
Expand Down Expand Up @@ -206,9 +207,9 @@ def send(self, input_data):
return self.__sendall(Command.RTDE_DATA_PACKAGE, config.pack(input_data))

def receive(self, binary=False):
"""Recieve the latest data package.
If muliple packages has been received, older ones are discarded
and only the newest one will be returned. Will block untill a package
"""Receive the latest data package.
If multiple packages has been received, older ones are discarded
and only the newest one will be returned. Will block until a package
is received or the connection is lost
"""
if self.__output_config is None:
Expand All @@ -218,8 +219,8 @@ def receive(self, binary=False):
return self.__recv(Command.RTDE_DATA_PACKAGE, binary)

def receive_buffered(self, binary=False, buffer_limit=None):
"""Recieve the next data package.
If muliple packages has been received they are buffered and will
"""Receive the next data package.
If multiple packages has been received they are buffered and will
be returned on subsequent calls to this function.
Returns None if no data is available.
"""
Expand Down Expand Up @@ -301,6 +302,9 @@ def has_data(self):
return len(readable) != 0

def __recv(self, command, binary=False):

previous_skipped_package_count = self.__skipped_package_count

while self.is_connected():
try:
self.__recv_to_buffer(DEFAULT_TIMEOUT)
Expand All @@ -321,16 +325,44 @@ def __recv(self, command, binary=False):
if len(self.__buf) >= 3 and command == Command.RTDE_DATA_PACKAGE:
next_packet_header = serialize.ControlHeader.unpack(self.__buf)
if next_packet_header.command == command:
_log.debug("skipping package(1)")
self.__skipped_package_count += 1
continue
if packet_header.command == command:
if (
self.__skipped_package_count
> previous_skipped_package_count
):
_log.debug(
"Total number of skipped packages increased to {}".format(
self.__skipped_package_count
)
)

if self.__warning_counter:
for warn in self.__warning_counter:
_log.debug(
"A total of {} packets with command {} received, before expected command.".format(
self.__warning_counter[warn], warn
)
)
self.__warning_counter.clear()

if binary:
return packet[1:]

return data
else:
_log.debug("skipping package(2)")
if not packet_header.command in self.__warning_counter:
_log.debug(
"Packet with command {} doesn't match the expected command {}. It will be skipped.".format(
packet_header.command, command
)
)
self.__warning_counter[packet_header.command] = 1
else:
self.__warning_counter[packet_header.command] = (
self.__warning_counter[packet_header.command] + 1
)
else:
break
raise RTDEException(" _recv() Connection lost ")
Expand Down
10 changes: 8 additions & 2 deletions rtde/serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import struct
import sys


class ControlHeader(object):
Expand Down Expand Up @@ -167,8 +168,13 @@ class DataConfig(object):
@staticmethod
def unpack_recipe(buf):
rmd = DataConfig()
rmd.id = struct.unpack_from(">B", buf)[0]
rmd.types = buf.decode("utf-8")[1:].split(",")
python_version = sys.version_info
if python_version.major == 2:
rmd.id = struct.unpack_from(">B", buf)[0]
rmd.types = buf.decode("utf-8")[1:].split(",")
else:
rmd.id = buf[0]
rmd.types = buf[1:].decode("utf-8").split(",")
rmd.fmt = ">B"
for i in rmd.types:
if i == "INT32":
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@
setup(
name="UrRtde",
packages=["rtde"],
version="2.7.2",
version="2.7.12",
description="Real-Time Data Exchange (RTDE) python client + examples",
)