Skip to content

Commit a6710af

Browse files
committed
Changed to implicit mastership - Tested RWS2 tests and added testing app - updated readme.
1 parent 148741c commit a6710af

File tree

6 files changed

+122
-93
lines changed

6 files changed

+122
-93
lines changed

README.md

Lines changed: 8 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
# abb_robot_client
1+
# abb_robot_client
2+
[![Python](https://img.shields.io/badge/python-3.8+-blue.svg)](https://github.com/rpiRobotics/abb_robot_client)
3+
4+
This package is forked from https://github.com/rpiRobotics/abb_robot_client and has been modified and modernized.
5+
26

3-
[![Python](https://img.shields.io/badge/python-3.6+-blue.svg)](https://github.com/rpiRobotics/abb_robot_client)
4-
![PyPI](https://img.shields.io/pypi/v/abb-robot-client)
57

68
Python package providing clients for ABB robots using RWS (Robot Web Services) and Externally Guided Motion (EGM).
7-
This package currently supports IRC5 controllers running RobotWare 6.xx. It does not support RobotWare 7+.
9+
This package supports IRC5 controllers running RobotWare 6.xx as well as Omnicore controllers running RobotWare 7+.
810

911
This package is typically used with [abb-motion-program-exec](https://pypi.org/project/abb-motion-program-exec/),
1012
which provides a higher level interface to generate motion programs. `abb-motion-program-exec` includes the ability
@@ -21,59 +23,12 @@ Documentation can be found at: https://abb_robot_client.readthedocs.org
2123

2224
## Installation
2325

24-
`abb-robot-client` is avaliable on PyPi. Use the `[aio]` option to include support for asyncio:
25-
26-
```
27-
pip install abb-robot-client[aio]
28-
```
29-
30-
## Examples
31-
32-
See the `examples/` directory for examples using the modules.
33-
34-
## Robot Raconteur Driver
35-
36-
The Robot Raconteur driver provides access to the features of RWS and EGM, along with standard Robot Raconteur
37-
data structures such as `RobotInfo` and `RobotState`. See `examples/robotraconteur` for examples using
38-
the Robot Raconteur driver. See `src/abb_robot_client/robotraconteur/experimental.abb_robot.rws.robdef` and
39-
`src/abb_robot_client/robotraconteur/experimental.abb_robot.egm.robdef` service definitions
40-
for full information on the objects and data types provided by the service.
41-
42-
### Installation
43-
44-
Install the `abb-robot-client` with the `[robotraconteur]` feature:
45-
46-
```bash
47-
python -m pip install abb-robot-client[robotraconteur]
48-
```
49-
50-
### Start the Driver
51-
52-
Start the driver:
53-
5426
```
55-
abb-robot-client-robotraconteur --robot-info-file=config\abb_1200_5_90_rws_default_config.yml --robot-url=http://127.0.0.1:80
27+
uv add https://github.com/Mesh-ch/abb_robot_client.git
5628
```
5729

58-
or
59-
60-
```
61-
python -m pip abb_robot_client.robotraconteur --robot-info-file=config\abb_1200_5_90_rws_default_config.yml --robot-url=http://127.0.0.1:80
62-
```
63-
64-
Change the `--robot-info-file=` to the appropriate Robot Raconteur format yaml file for your robot,
65-
and change `--robot-url=` to the URL of the IRC5 robot controller.
66-
67-
If EGM is used, the EGM must be configured on the robot controller to point to the IP address of the
68-
computer running the driver, on port 6510.
69-
70-
### Connection Info
30+
## Tests
7131

72-
- URL: `rr+tcp://localhost:59926?service=robot`
73-
- Node Name: `experimental.abb_rws_robot.robot`
74-
- Device Name: `abb_robot_rws`
75-
- Service Name: `robot`
76-
- Root Object Type: `experimental.abb_robot.rws.ABBRWSRobot`
7732

7833
## License
7934

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ build-backend = "hatchling.build"
2525
[dependency-groups]
2626
dev = [
2727
"pytest>=8.4.2",
28+
"textual>=6.1.0",
2829
]
2930

3031
[project.urls]
@@ -35,4 +36,4 @@ dev = [
3536
[tool.pytest.ini_options]
3637
testpaths = [
3738
"tests"
38-
]
39+
]

src/abb_robot_client/rws2.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -190,36 +190,36 @@ def start(self, cycle: Optional[str] = 'asis', tasks: Optional[list[str]] = ["T_
190190
self.deactivate_task(rob_task.name)
191191

192192
payload={"regain": "continue", "execmode": "continue", "cycle": cycle, "condition": "none", "stopatbp": "disabled", "alltaskbytsp": "true"}
193-
res=self._do_post("rw/rapid/execution/start", payload)
194-
193+
res=self._do_post("rw/rapid/execution/start?mastership=implicit", payload)
194+
195195
def activate_task(self, task: str):
196196
"""
197197
Activate a RAPID task
198198
199199
:param task: The name of the task to activate
200200
"""
201-
self._do_post("rw/rapid/tasks/activate", data={"task": task})
202-
203-
def deactivate_task(self, task: str) -> None:
201+
self._do_post("rw/rapid/tasks/activate?mastership=implicit", data={"task": task})
202+
203+
def deactivate_task(self, task: str) -> None:
204204
"""
205205
Deactivate a RAPID task
206206
207207
:param task: The name of the task to deactivate
208208
"""
209-
self._do_post(f"rw/rapid/tasks/{task}/deactivate", data={"task": task})
209+
self._do_post(f"rw/rapid/tasks/{task}/deactivate?mastership=implicit", data={"task": task})
210210

211211
def stop(self):
212212
"""
213213
Stop RAPID execution of normal tasks
214214
"""
215215
payload={"stopmode": "stop"}
216-
res=self._do_post("rw/rapid/execution/stop", payload)
216+
res=self._do_post("rw/rapid/execution/stop?mastership=implicit", payload)
217217

218218
def resetpp(self):
219219
"""
220220
Reset RAPID program pointer to main in normal tasks
221221
"""
222-
res=self._do_post("rw/rapid/execution/resetpp")
222+
res=self._do_post("rw/rapid/execution/resetpp?mastership=implicit")
223223

224224
def get_ramdisk_path(self) -> str:
225225
"""
@@ -257,9 +257,7 @@ def get_controller_state(self) -> str:
257257
def set_controller_state(self, ctrl_state):
258258
"""Possible ctrl-states to set are `motoroff` or `motoron`"""
259259
payload = {"ctrl-state": ctrl_state}
260-
self.request_mastership()
261-
res=self._do_post("rw/panel/ctrl-state", payload)
262-
self.release_mastership()
260+
res=self._do_post("rw/panel/ctrl-state?mastership=implicit", payload)
263261

264262
def set_motors_on(self):
265263
"""
@@ -326,7 +324,7 @@ def set_digital_io(self, signal: str, value: bool|int, network: str='Local', uni
326324
"""
327325
lvalue = '1' if bool(value) else '0'
328326
payload={'lvalue': lvalue}
329-
res=self._do_post(f"rw/iosystem/signals/{network}/{unit}/{signal}", payload)
327+
res=self._do_post(f"rw/iosystem/signals/{network}/{unit}/{signal}?mastership=implicit", payload)
330328

331329
def get_analog_io(self, signal: str, network: str='Local', unit: str='DRV_1') -> float:
332330
"""
@@ -383,7 +381,7 @@ def get_rapid_variable(self, var: str, task: str = "T_ROB1") -> str:
383381
:param task: The task containing the pers variable
384382
:return: The pers variable encoded as a string
385383
"""
386-
res_json = self._do_get(f"rw/rapid/symbol/RAPID/{task}/{var}/data")
384+
res_json = self._do_get(f"rw/rapid/symbol/RAPID/{task}/{var}/data?mastership=implicit")
387385
state = res_json["state"][0]["value"]
388386
return state
389387

@@ -401,14 +399,15 @@ def set_rapid_variable(self, var: str, value: str, task: str = "T_ROB1"):
401399
else:
402400
var1 = var
403401
self.request_mastership()
404-
res=self._do_post(f"rw/rapid/symbol/RAPID/{var1}/data/", payload)
402+
res=self._do_post(f"rw/rapid/symbol/RAPID/{var1}/data?mastership=implicit", payload)
405403
self.release_mastership()
406404

407405
def read_file(self, filename: str, directory: str = "$HOME") -> bytes:
408406
"""
409407
Read a file off the controller
410408
411409
:param filename: The relative path to the filename to read, e.g.: $HOME/...
410+
:param directory: The directory to read the file from, e.g. $HOME
412411
:return: The file bytes
413412
"""
414413
res_json = self._do_get_raw(f"fileservice/{directory}/{filename}")
@@ -426,6 +425,7 @@ def upload_file(self, filename: str, contents: str, directory: str = "$HOME") ->
426425
427426
:param filename: The filename to write
428427
:param contents: The file content as str
428+
:param directory: The directory to write the file to, e.g. $HOME
429429
"""
430430
url=f"{self.base_url}/fileservice/{directory}/{filename}"
431431
header = {"Content-Type": "text/plain;v=2.0"}
@@ -692,7 +692,7 @@ def set_speedratio(self, speedratio: float):
692692
:param speedratio: The new speed ratio between 0% - 100%
693693
"""
694694
payload = {"speed-ratio": str(speedratio)}
695-
self._do_post(f"rw/panel/speedratio?mastership=implicit", payload)
695+
self._do_post("rw/panel/speedratio?mastership=implicit", payload)
696696

697697

698698
# def send_ipc_message(self, target_queue: str, data: str, queue_name: str, cmd: int=111, userdef: int=1, msgtype: int=1 ):

tests/RWS2_test_app.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""
2+
A textualize-based interactive CLI to run test functions from test_RWS2_POST.py.
3+
"""
4+
import asyncio
5+
from textual.app import App, ComposeResult
6+
from textual.widgets import Header, Footer, Button, Static, ListView, ListItem
7+
from textual.containers import Container
8+
import importlib
9+
import inspect
10+
11+
12+
# Import the test module and get test functions
13+
test_module = importlib.import_module("test_RWS2_POST")
14+
15+
from abb_robot_client.rws2 import RWS2
16+
17+
def get_test_functions():
18+
return [
19+
(name, func)
20+
for name, func in inspect.getmembers(test_module, inspect.isfunction)
21+
if name.startswith("test_")
22+
]
23+
24+
class TestResult(Static):
25+
pass
26+
27+
class TestRWS2POST(App):
28+
CSS_PATH = None
29+
BINDINGS = [ ("q", "quit", "Quit") ]
30+
31+
def __init__(self, **kwargs):
32+
super().__init__(**kwargs)
33+
self.client = RWS2()
34+
35+
def compose(self) -> ComposeResult:
36+
yield Header()
37+
yield Container(
38+
ListView(*[ListItem(Button(name, id=name)) for name, _ in get_test_functions()], id="test-list"),
39+
TestResult("Select a test to run.", id="result"),
40+
)
41+
yield Footer()
42+
43+
async def on_button_pressed(self, event: Button.Pressed) -> None:
44+
test_name = event.button.id
45+
test_func = dict(get_test_functions())[test_name]
46+
result_widget = self.query_one("#result", TestResult)
47+
try:
48+
sig = inspect.signature(test_func)
49+
if len(sig.parameters) == 1:
50+
await asyncio.to_thread(test_func, self.client)
51+
else:
52+
await asyncio.to_thread(test_func)
53+
result_widget.update(f"[green]Test {test_name} passed![/green]")
54+
except Exception as e:
55+
result_widget.update(f"[red]Test {test_name} failed: {e}[/red]")
56+
57+
if __name__ == "__main__":
58+
app = TestRWS2POST()
59+
app.run()

tests/test_RWS2_GET.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def test_read_event_log(client):
6464
def test_get_robtarget(client):
6565
robtarget = client.get_robtarget()
6666
assert isinstance(robtarget, RobTarget), f"Unexpected robtarget type: {type(robtarget)}"
67-
67+
print(robtarget)
6868

6969
def test_get_speedratio(client):
7070
speedratio = client.get_speedratio()
@@ -87,20 +87,20 @@ def test_get_speedratio(client):
8787
if __name__ == "__main__":
8888
rws = RWS2()
8989
#### GET REQUESTS ####
90-
test_get_tasks(rws)
90+
# test_get_tasks(rws)
9191
test_get_jointtargets(rws)
92-
test_get_execution_state(rws)
93-
test_get_ramdisk_paths(rws)
94-
test_get_controller_state(rws)
95-
test_get_operation_mode(rws)
96-
test_get_digital_io(rws)
97-
test_get_analog_io(rws)
98-
test_get_rapid_variable(rws)
99-
test_get_rapid_variable_num(rws)
100-
test_read_file(rws)
101-
test_read_event_log(rws)
102-
test_read_event_log(rws)
92+
# test_get_execution_state(rws)
93+
# test_get_ramdisk_paths(rws)
94+
# test_get_controller_state(rws)
95+
# test_get_operation_mode(rws)
96+
# test_get_digital_io(rws)
97+
# test_get_analog_io(rws)
98+
# test_get_rapid_variable(rws)
99+
# test_get_rapid_variable_num(rws)
100+
# test_read_file(rws)
101+
# test_read_event_log(rws)
102+
# test_read_event_log(rws)
103103
test_get_robtarget(rws)
104-
test_get_speedratio(rws)
104+
# test_get_speedratio(rws)
105105
# test_get_ipc_queue(rws)
106106
# test_ipc_message(rws)

tests/test_RWS2_POST.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
import time
23
from abb_robot_client.rws2 import RWS2, EventLogEntry, JointTarget, RAPIDExecutionState, RobTarget
34

45
@pytest.fixture(scope="module")
@@ -13,6 +14,10 @@ def client():
1314
# for msg in messages:
1415
# print("IPC Message:", msg)
1516

17+
def test_resetpp(client):
18+
client.resetpp()
19+
print("Program pointer reset.")
20+
1621
def test_set_speedratio(client):
1722
original_speedratio = client.get_speedratio()
1823
new_speedratio = 50 if original_speedratio != 50 else 60
@@ -22,6 +27,17 @@ def test_set_speedratio(client):
2227
# Restore original speed ratio
2328
client.set_speedratio(100)
2429
assert client.get_speedratio() == 100
30+
31+
def test_set_digital_io(client):
32+
io_state = client.get_digital_io("motion_program_error")
33+
new_state = '1' if io_state == '0' else '0'
34+
client.set_digital_io("motion_program_error", new_state)
35+
updated_state = client.get_digital_io("motion_program_error")
36+
assert updated_state == int(new_state)
37+
time.sleep(0.5)
38+
# Restore original state
39+
client.set_digital_io("motion_program_error", io_state)
40+
assert client.get_digital_io("motion_program_error") == int(io_state)
2541

2642
def test_start(client):
2743
client.start()
@@ -65,13 +81,11 @@ def test_toggle_motors(client):
6581
else:
6682
raise Exception(f"Controller in unexpected state: {motor_state}")
6783

68-
if __name__ == "__main__":
69-
rws = RWS2()
70-
# rws.request_mastership()
71-
#### POST REQUESTS ####
72-
# test_set_speedratio(rws)
73-
# test_start(rws)
74-
# test_upload_delete_file(rws)
75-
# test_set_rapid_variable_num(rws)
76-
77-
84+
def test_request_mastership(client):
85+
client.request_mastership()
86+
87+
def test_release_mastership(client):
88+
result = client.request_mastership()
89+
time.sleep(1) # Simulate some operations while holding mastership
90+
result = client.release_mastership()
91+
print(result)

0 commit comments

Comments
 (0)