robotframework-parrot-touchcontroller 1.0.0__tar.gz
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.
- robotframework_parrot_touchcontroller-1.0.0/PKG-INFO +34 -0
- robotframework_parrot_touchcontroller-1.0.0/README.md +19 -0
- robotframework_parrot_touchcontroller-1.0.0/parrot/TouchController.py +249 -0
- robotframework_parrot_touchcontroller-1.0.0/parrot/touch/adbtouch.py +84 -0
- robotframework_parrot_touchcontroller-1.0.0/parrot/touch/axitouch.py +185 -0
- robotframework_parrot_touchcontroller-1.0.0/parrot/touch/base.py +32 -0
- robotframework_parrot_touchcontroller-1.0.0/parrot/touch/dummy.py +35 -0
- robotframework_parrot_touchcontroller-1.0.0/parrot/touch/usbhidtouch.py +120 -0
- robotframework_parrot_touchcontroller-1.0.0/pyproject.toml +32 -0
- robotframework_parrot_touchcontroller-1.0.0/robotframework_parrot_touchcontroller.egg-info/PKG-INFO +34 -0
- robotframework_parrot_touchcontroller-1.0.0/robotframework_parrot_touchcontroller.egg-info/SOURCES.txt +13 -0
- robotframework_parrot_touchcontroller-1.0.0/robotframework_parrot_touchcontroller.egg-info/dependency_links.txt +1 -0
- robotframework_parrot_touchcontroller-1.0.0/robotframework_parrot_touchcontroller.egg-info/requires.txt +3 -0
- robotframework_parrot_touchcontroller-1.0.0/robotframework_parrot_touchcontroller.egg-info/top_level.txt +1 -0
- robotframework_parrot_touchcontroller-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: robotframework-parrot-touchcontroller
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Touch control api to use within the Robot Framework ecosystem
|
|
5
|
+
Author-email: Zilogic Systems <code@zilogic.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/zilogic-systems/parrot
|
|
8
|
+
Project-URL: Repository, https://github.com//zilogic-systems/parrot.git
|
|
9
|
+
Project-URL: Issues, https://github.com/zilogic-systems/parrot/issues
|
|
10
|
+
Requires-Python: >=3.6
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: robotframework
|
|
13
|
+
Requires-Dist: pyyaml
|
|
14
|
+
Requires-Dist: robotframework-parrot-adbcontroller
|
|
15
|
+
|
|
16
|
+
# Touch Controller
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
To install from package registry use the following command:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
$ pip install robotframework-parrot-touchcontroller
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
To install from git repo use the following command:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
$ pip install .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Example
|
|
33
|
+
|
|
34
|
+
Example of using the library in Robot Framework is available `example.robot`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Touch Controller
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
To install from package registry use the following command:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
$ pip install robotframework-parrot-touchcontroller
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
To install from git repo use the following command:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
$ pip install .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Example
|
|
18
|
+
|
|
19
|
+
Example of using the library in Robot Framework is available `example.robot`.
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
from robot.api.deco import keyword
|
|
2
|
+
from parrot.touch.dummy import DummyTouch
|
|
3
|
+
from parrot.touch.axitouch import AxiDrawTouch
|
|
4
|
+
from parrot.touch.base import Error
|
|
5
|
+
from parrot.touch.adbtouch import ADBTouch
|
|
6
|
+
from parrot.touch.usbhidtouch import UsbHidTouch
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
|
|
9
|
+
class TouchError(Exception):
|
|
10
|
+
"""Raised when a device-level touch error occurs."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TouchController:
|
|
14
|
+
"""
|
|
15
|
+
A Robot Framework library for simulating touch input across various
|
|
16
|
+
device types through a unified interface.
|
|
17
|
+
|
|
18
|
+
== Configuration ==
|
|
19
|
+
|
|
20
|
+
Touch devices are configured using the `Touch Setup` keyword with a dictionary
|
|
21
|
+
typically loaded from a YAML file (e.g. `config.yaml`) by importing the library
|
|
22
|
+
and variables into your test suite. Each entry in the configuration must include
|
|
23
|
+
a `type` field that specifies the device type. Supported types include:
|
|
24
|
+
|
|
25
|
+
- `UsbHidTouch`: USB HID-based touch simulation over TCP.
|
|
26
|
+
- `AxiDrawTouch`: Physical touch simulation using a pen plotter.
|
|
27
|
+
- `ADBTouch`: Android device touch simulation over ADB.
|
|
28
|
+
|
|
29
|
+
=== UsbHidTouch ===
|
|
30
|
+
Simulates touch over a USB HID device exposed via a TCP server.
|
|
31
|
+
Supported configs:
|
|
32
|
+
- `host`: IP address or hostname of the USB HID server (e.g., `127.0.0.1`)
|
|
33
|
+
- `port`: TCP port number (e.g., `8000`)
|
|
34
|
+
- `width`, `height`: Touch coordinate resolution (e.g., 1024x600)
|
|
35
|
+
|
|
36
|
+
=== AxiDrawTouch ===
|
|
37
|
+
Controls an AxiDraw pen plotter to simulate physical touchscreen input.
|
|
38
|
+
Supported configs:
|
|
39
|
+
- `device`: Serial device path (e.g., `/dev/ttyUSB0`)
|
|
40
|
+
- `width`, `height`: Touch surface resolution in pixels
|
|
41
|
+
- `start_pos`: Physical start coordinates (e.g., `[0, 0]`)
|
|
42
|
+
- `end_pos`: Physical end coordinates defining working area (e.g., `[8.66, 5.12]`)
|
|
43
|
+
|
|
44
|
+
=== ADBTouch ===
|
|
45
|
+
Simulates touch events on an Android device using ADB commands.
|
|
46
|
+
Supported configs:
|
|
47
|
+
- `ip`: IP address of the Android device (e.g., `192.168.43.168`)
|
|
48
|
+
|
|
49
|
+
=== Configuration Sample (config.yaml) ===
|
|
50
|
+
|
|
51
|
+
The configuration file is written in YAML format and contains a top-level key
|
|
52
|
+
`TouchConfig` with device definitions:
|
|
53
|
+
|
|
54
|
+
| TouchConfig:
|
|
55
|
+
| T1:
|
|
56
|
+
| type: UsbHidTouch
|
|
57
|
+
| host: 127.0.0.1
|
|
58
|
+
| port: 8000
|
|
59
|
+
| width: 1024
|
|
60
|
+
| height: 600
|
|
61
|
+
|
|
|
62
|
+
| T2:
|
|
63
|
+
| type: AxiDrawTouch
|
|
64
|
+
| device: /dev/ttyUSB0
|
|
65
|
+
| width: 1024
|
|
66
|
+
| height: 600
|
|
67
|
+
| start_pos: [0, 0]
|
|
68
|
+
| end_pos: [8.66, 5.12]
|
|
69
|
+
|
|
|
70
|
+
| T3:
|
|
71
|
+
| type: ADBTouch
|
|
72
|
+
| ip: 192.168.43.168
|
|
73
|
+
|
|
74
|
+
= General Usage =
|
|
75
|
+
|
|
76
|
+
Setup and initialize touch devices from a YAML configuration. Simulate touch input such as tap and swipe gestures
|
|
77
|
+
across multiple supported devices. This library supports both real physical systems and simulated touch environments,
|
|
78
|
+
making it suitable for automated device interaction and UI testing.
|
|
79
|
+
|
|
80
|
+
= Example =
|
|
81
|
+
|
|
82
|
+
| ***** Settings *****
|
|
83
|
+
| Library parrot.TouchController
|
|
84
|
+
| Variables config.yaml
|
|
85
|
+
|
|
|
86
|
+
| ***** Test Cases *****
|
|
87
|
+
| Run Touch Tests
|
|
88
|
+
| Touch Setup ${TouchConfig} # Initializes all touch devices from the YAML configuration.
|
|
89
|
+
| Tap T1 640 500 0.2 # Simulates a tap at (640, 500) for 0.2 seconds on T1.
|
|
90
|
+
| Swipe T1 100 100 600 400 0.2 # Simulates swipe from (100,100) to (600,400).
|
|
91
|
+
| Touch Command T1 move 0 1 # Moves the touch to specified screen coordinates.
|
|
92
|
+
| Touch Command T1 home # Returns the axitouch to its home position.
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
ROBOT_LIBRARY_SCOPE = 'GLOBAL'
|
|
98
|
+
|
|
99
|
+
def __init__(self) -> None:
|
|
100
|
+
self.devices: Dict[str, Any] = {}
|
|
101
|
+
|
|
102
|
+
@keyword("Touch Setup")
|
|
103
|
+
def setup(self, config: Dict[str, Dict[str, Any]]) -> None:
|
|
104
|
+
"""Initializes and configures touch controller devices.
|
|
105
|
+
Loads controller instances from the given `Configuration` dictionary
|
|
106
|
+
and prepares them for input simulation.
|
|
107
|
+
|
|
108
|
+
*Syntax*
|
|
109
|
+
|
|
110
|
+
| `Touch Setup` | <CONFIG> |
|
|
111
|
+
|
|
112
|
+
*Examples*
|
|
113
|
+
|
|
114
|
+
| `Touch Setup` | ${TouchConfig} | # Loads and initializes all camera devices defined in the `${CameraConfig}` dictionary. |
|
|
115
|
+
"""
|
|
116
|
+
if not isinstance(config, dict):
|
|
117
|
+
raise Error("Expected a dictionary of controller configurations.")
|
|
118
|
+
|
|
119
|
+
controller_classes: Dict[str, Any] = {
|
|
120
|
+
"DummyTouch": DummyTouch,
|
|
121
|
+
"AxiDrawTouch": AxiDrawTouch,
|
|
122
|
+
"ADBTouch": ADBTouch,
|
|
123
|
+
"UsbHidTouch": UsbHidTouch,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for name, dev in config.items():
|
|
127
|
+
class_type = dev.get("type")
|
|
128
|
+
|
|
129
|
+
if not class_type or not name:
|
|
130
|
+
raise Error("Each TouchController entry must have 'type' and 'name' fields.")
|
|
131
|
+
|
|
132
|
+
if class_type not in controller_classes:
|
|
133
|
+
raise Error(f"Controller class '{class_type}' is not supported.")
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
device = controller_classes[class_type](dev)
|
|
137
|
+
device.touch_init()
|
|
138
|
+
except Error as err:
|
|
139
|
+
raise TouchError(f"Failed to initialize controller '{name}' of type '{class_type}': {err}") from err
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
self.devices[name] = device
|
|
143
|
+
|
|
144
|
+
print(f"[INFO] Loaded controllers: {list(self.devices.keys())}")
|
|
145
|
+
|
|
146
|
+
@keyword("Tap")
|
|
147
|
+
def send_touch(self, device: str, x: float, y: float, tap_time: float = 0.2) -> None:
|
|
148
|
+
"""
|
|
149
|
+
Simulates a tap action on the given device at specified screen coordinates.
|
|
150
|
+
Requires the device name, X and Y coordinates in pixels, and an
|
|
151
|
+
optional tap duration (default: 0.2 seconds).
|
|
152
|
+
|
|
153
|
+
*Syntax*
|
|
154
|
+
|
|
155
|
+
| `Tap` | <DEVICE_NAME> | <X> | <Y> | <TAP_TIME> |
|
|
156
|
+
|
|
157
|
+
*Examples*
|
|
158
|
+
|
|
159
|
+
| `Tap` | T1 | 640 | 500 | 0.2 | # Simulates a tap at (640, 500) on device `T1` with a duration of 0.2 seconds. |
|
|
160
|
+
|
|
161
|
+
"""
|
|
162
|
+
controller = self.devices.get(device)
|
|
163
|
+
if not controller:
|
|
164
|
+
raise TouchError(f"Touch controller '{device}' not found.")
|
|
165
|
+
controller.touch_tap(float(x), float(y), float(tap_time))
|
|
166
|
+
|
|
167
|
+
@keyword("Swipe")
|
|
168
|
+
def send_swipe(
|
|
169
|
+
self,
|
|
170
|
+
device: str,
|
|
171
|
+
x1: float,
|
|
172
|
+
y1: float,
|
|
173
|
+
x2: float,
|
|
174
|
+
y2: float,
|
|
175
|
+
) -> None:
|
|
176
|
+
"""
|
|
177
|
+
Simulates a swipe gesture on the given device from (x1, y1) to (x2, y2).
|
|
178
|
+
Requires the device name, starting and ending coordinates in pixels.
|
|
179
|
+
|
|
180
|
+
*Syntax*
|
|
181
|
+
|
|
182
|
+
| <DEVICE_NAME> | <DEVICE_NAME> | <X1> | <Y1> | <X2> | <Y2> |
|
|
183
|
+
|
|
184
|
+
*Examples*
|
|
185
|
+
|
|
186
|
+
| `Swipe` | T1 | 100 | 100 | 600 | 400 | 0.3 | # Simulates a swipe on device `T1` from (100, 100) to (600, 400) |
|
|
187
|
+
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
controller = self.devices.get(device)
|
|
191
|
+
if not controller:
|
|
192
|
+
raise TouchError(f"Touch controller '{device}' not found.")
|
|
193
|
+
controller.touch_swipe(float(x1), float(y1), float(x2), float(y2))
|
|
194
|
+
|
|
195
|
+
@keyword("Touch Command")
|
|
196
|
+
def command(self, device, command, *args):
|
|
197
|
+
"""
|
|
198
|
+
Executes a backend-specific touch command on the given device controller.
|
|
199
|
+
|
|
200
|
+
This keyword is used to send a command (e.g., `Move`, `Home`) to a specific device.
|
|
201
|
+
The available commands and required arguments depends on the backend implementation
|
|
202
|
+
of the touch controller. Currently only the AxiDrawTouch backend has commands.
|
|
203
|
+
|
|
204
|
+
AxiDrawTouch backend supports the following commands.
|
|
205
|
+
|
|
206
|
+
- `Move` — Moves to the specified position (in inches) without touching.
|
|
207
|
+
Requires two arguments: `x`, `y`.
|
|
208
|
+
- `Home` — Returns the touch to its home (default) position.
|
|
209
|
+
Takes no arguments.
|
|
210
|
+
|
|
211
|
+
*Syntax*
|
|
212
|
+
|
|
213
|
+
| Touch Command | <DEVICE_NAME> | <COMMAND> | [<ARG1>] | [<ARG2>] |
|
|
214
|
+
|
|
215
|
+
*Examples*
|
|
216
|
+
|
|
217
|
+
| Touch Command | T1 | move | 200 | 300 |
|
|
218
|
+
| Touch Command | T1 | home |
|
|
219
|
+
|
|
220
|
+
"""
|
|
221
|
+
controller = self.devices.get(device)
|
|
222
|
+
if not controller:
|
|
223
|
+
raise TouchError(f"Touch controller '{device}' not found.")
|
|
224
|
+
controller.touch_command(command, *args)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@keyword("Touch Set Param")
|
|
228
|
+
def set_param_auto_home(self, device: str, param: str, value: str) -> None:
|
|
229
|
+
"""
|
|
230
|
+
Sets values to configure runtime parameters for Touch control devices.
|
|
231
|
+
|
|
232
|
+
Currently, this keyword supports only for `AxiDrawTouch` type devices,
|
|
233
|
+
|
|
234
|
+
| *Parameter* | *Description* | *Example Value* |
|
|
235
|
+
| `auto_home` | Specifies the touch action to be directed to the home position | "True or False" |
|
|
236
|
+
|
|
237
|
+
*Syntax*
|
|
238
|
+
|
|
239
|
+
| `Touch Set Param` | <DEVICE_NAME> | auto_home | <VALUE> |
|
|
240
|
+
|
|
241
|
+
*Examples*
|
|
242
|
+
|
|
243
|
+
| `Touch Set Param` | T1 | auto_home | True | # Enables auto-home on the touch controller device. |
|
|
244
|
+
| `Touch Set Param` | T2 | auto_home | False | # Disables auto-home on the touch controller device. |
|
|
245
|
+
"""
|
|
246
|
+
controller = self.devices.get(device)
|
|
247
|
+
if not controller:
|
|
248
|
+
raise TouchError(f"Touch controller '{device}' not found.")
|
|
249
|
+
controller.set_param(param, value)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from parrot import ADBController
|
|
2
|
+
from parrot.touch.base import BaseTouch, Error
|
|
3
|
+
|
|
4
|
+
class ADBTouch(BaseTouch):
|
|
5
|
+
"""
|
|
6
|
+
A touch controller implementation using ADB (Android Debug Bridge).
|
|
7
|
+
|
|
8
|
+
This class provides methods to simulate touch gestures such as tap and swipe
|
|
9
|
+
on Android devices over a network or USB using ADB shell commands.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, config: dict):
|
|
13
|
+
"""
|
|
14
|
+
Initializes the ADB touch controller.
|
|
15
|
+
|
|
16
|
+
The configuration dictionary must contain:
|
|
17
|
+
- `ip`: IP address of the ADB-connected device
|
|
18
|
+
|
|
19
|
+
Raises:
|
|
20
|
+
Error: If required configuration is missing or initialization fails.
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
self.device_ip = config["ip"]
|
|
24
|
+
self.adb = ADBController.ADBController()
|
|
25
|
+
except KeyError as err:
|
|
26
|
+
raise Error(f"Missing required configuration key: {err}") from err
|
|
27
|
+
except Exception as err:
|
|
28
|
+
raise Error(f"Initialization failed: {err}") from err
|
|
29
|
+
|
|
30
|
+
def touch_init(self):
|
|
31
|
+
"""
|
|
32
|
+
Initializes the ADB device connection using the provided IP.
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
Error: If the device connection fails.
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
self.adb.adb_ip_connect(self.device_ip)
|
|
39
|
+
print(f"ADBTouch: Connected to device {self.device_ip}")
|
|
40
|
+
except Exception as err:
|
|
41
|
+
raise Error("Initialization failed") from err
|
|
42
|
+
|
|
43
|
+
def touch_tap(self, x, y, tap_time):
|
|
44
|
+
"""
|
|
45
|
+
Simulates a tap at the given screen coordinates using ADB shell.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
x (int | float): X-coordinate in pixels.
|
|
49
|
+
y (int | float): Y-coordinate in pixels.
|
|
50
|
+
tap_time: (unused) Compatibility with base method signature.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
Error: If the tap command fails.
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
cmd = ["input", "tap", str(int(x)), str(int(y))]
|
|
57
|
+
self.adb.adb_shell(cmd)
|
|
58
|
+
print(f"ADBTouch: Tap at ({x}, {y})")
|
|
59
|
+
except Exception as err:
|
|
60
|
+
raise Error(f"Touch tap failed at ({x}, {y})") from err
|
|
61
|
+
|
|
62
|
+
def touch_swipe(self, startx, starty, endx, endy, swipe_time):
|
|
63
|
+
"""
|
|
64
|
+
Simulates a swipe gesture from start to end coordinates using ADB shell.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
startx (int | float): Start X-coordinate in pixels.
|
|
68
|
+
starty (int | float): Start Y-coordinate in pixels.
|
|
69
|
+
endx (int | float): End X-coordinate in pixels.
|
|
70
|
+
endy (int | float): End Y-coordinate in pixels.
|
|
71
|
+
swipe_time: (unused) Compatibility with base method signature.
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
Error: If the swipe command fails.
|
|
75
|
+
"""
|
|
76
|
+
try:
|
|
77
|
+
cmd = ["input", "swipe", str(int(startx)), str(int(starty)), str(int(endx)), str(int(endy))]
|
|
78
|
+
self.adb.adb_shell(cmd)
|
|
79
|
+
print(f"ADBTouch: Swipe from ({startx}, {starty}) to ({endx}, {endy})")
|
|
80
|
+
except Exception as err:
|
|
81
|
+
raise Error(f"Swipe failed from ({startx}, {starty}) to ({endx}, {endy})") from err
|
|
82
|
+
|
|
83
|
+
def touch_command(self, cmd, *args):
|
|
84
|
+
raise Error("Commands are not supported by the ADBTouch backend")
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
from robot.api.deco import keyword
|
|
2
|
+
from parrot.touch.base import BaseTouch,Error
|
|
3
|
+
from typing import Tuple, Any
|
|
4
|
+
|
|
5
|
+
import xmlrpc.client
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AxiTouchError(Exception):
|
|
9
|
+
"""Represents an error in the AxiDrawTouch controller."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AxiDrawTouch(BaseTouch):
|
|
13
|
+
"""A touch controller implementation using the AxiDraw plotting device.
|
|
14
|
+
|
|
15
|
+
This class provides methods to simulate touch gestures such as tap and swipe
|
|
16
|
+
by controlling an AxiDraw plotter based on screen coordinates.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
PROPERTIES = ["auto_home"]
|
|
20
|
+
|
|
21
|
+
def __init__(self, config: dict[str, Any]) -> None:
|
|
22
|
+
"""Initializes the AxiDraw touch controller.
|
|
23
|
+
|
|
24
|
+
The configuration dictionary must contain:
|
|
25
|
+
- `width`: screen width in pixels
|
|
26
|
+
- `height`: screen height in pixels
|
|
27
|
+
- `start_pos`: tuple (x, y) of starting cartesian coordinates (in inches)
|
|
28
|
+
- `end_pos`: tuple (x, y) of ending cartesian coordinates (in inches)
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
AxiTouchError: If initialization or connection to AxiDraw fails.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
uri = f"http://{config['host']}:{config['port']}"
|
|
36
|
+
self.screen_width_px: int = config["width"]
|
|
37
|
+
self.screen_height_px: int = config["height"]
|
|
38
|
+
self.cartesian_start: Tuple[float, float] = config["start_pos"]
|
|
39
|
+
self.cartesian_end: Tuple[float, float] = config["end_pos"]
|
|
40
|
+
|
|
41
|
+
self.auto_home = False # Flag to track auto-home behavior
|
|
42
|
+
|
|
43
|
+
# Optional: configure pen heights and speed
|
|
44
|
+
pen_pos_up = 75
|
|
45
|
+
pen_pos_down = 40
|
|
46
|
+
speed_pendown = 100
|
|
47
|
+
speed_penup = 100
|
|
48
|
+
accel = 75
|
|
49
|
+
|
|
50
|
+
self.axi = xmlrpc.client.ServerProxy(uri)
|
|
51
|
+
|
|
52
|
+
self.axi.connect(accel,
|
|
53
|
+
pen_pos_up, pen_pos_down,
|
|
54
|
+
speed_penup, speed_pendown)
|
|
55
|
+
|
|
56
|
+
except KeyError as err:
|
|
57
|
+
raise Error(f"Missing required configuration key: {err}") from err
|
|
58
|
+
except Exception as err:
|
|
59
|
+
raise Error(f"Initialization failed: {err}") from err
|
|
60
|
+
|
|
61
|
+
def convert_to_inches(self, x: float, y: float) -> Tuple[float, float]:
|
|
62
|
+
x = float(x)
|
|
63
|
+
y = float(y)
|
|
64
|
+
sx, sy = self.cartesian_start
|
|
65
|
+
ex, ey = self.cartesian_end
|
|
66
|
+
x_scale = (ex - sx) / self.screen_width_px
|
|
67
|
+
y_scale = (ey - sy) / self.screen_height_px
|
|
68
|
+
return round(sx + x * x_scale, 3), round(sy + y * y_scale, 3)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def touch_init(self) -> None:
|
|
72
|
+
"""Initialize the pen by lifting it up and moving to origin (0, 0).
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
AxiTouchError: If initialization movement fails.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
self.axi.penup()
|
|
80
|
+
self.axi.home()
|
|
81
|
+
print("AxiDrawTouch: Initialized and moved to origin")
|
|
82
|
+
except Exception as err:
|
|
83
|
+
raise Error("Initialization failed") from err
|
|
84
|
+
|
|
85
|
+
def touch_tap(self, x: float, y: float, tap_time) -> None:
|
|
86
|
+
"""Simulate a tap at the given screen coordinates.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
x (int | float): X-coordinate in pixels.
|
|
90
|
+
y (int | float): Y-coordinate in pixels.
|
|
91
|
+
tap_time (float, optional): Duration to hold pen down. Defaults to 0.2s.
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
AxiTouchError: If the tap operation fails.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
px, py = self.convert_to_inches(x, y)
|
|
99
|
+
print(px, py)
|
|
100
|
+
self.axi.tap(px, py, tap_time)
|
|
101
|
+
if self.auto_home:
|
|
102
|
+
self.axi.home()
|
|
103
|
+
print(f"AxiDrawTouch: Tap at ({x}, {y}) for {tap_time}s")
|
|
104
|
+
except Exception as err:
|
|
105
|
+
raise Error(f"Touch tap failed at ({x}, {y})") from err
|
|
106
|
+
|
|
107
|
+
def touch_swipe(
|
|
108
|
+
self,
|
|
109
|
+
startx: float,
|
|
110
|
+
starty: float,
|
|
111
|
+
endx: float,
|
|
112
|
+
endy: float
|
|
113
|
+
) -> None:
|
|
114
|
+
"""Simulate a swipe gesture from start to end coordinates.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
startx (int | float): Start X-coordinate in pixels.
|
|
118
|
+
starty (int | float): Start Y-coordinate in pixels.
|
|
119
|
+
endx (int | float): End X-coordinate in pixels.
|
|
120
|
+
endy (int | float): End Y-coordinate in pixels.
|
|
121
|
+
swipe_time (float, optional): Duration of the swipe. Defaults to 0.2s.
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
AxiTouchError: If the swipe operation fails.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
psx, psy = self.convert_to_inches(startx, starty)
|
|
129
|
+
pex, pey = self.convert_to_inches(endx, endy)
|
|
130
|
+
self.axi.swipe(psx, psy, pex, pey)
|
|
131
|
+
print(f"AxiDrawTouch: Swipe from ({startx}, {starty}) to ({endx}, {endy})")
|
|
132
|
+
except Exception as err:
|
|
133
|
+
raise Error(f"Swipe failed from ({startx}, {starty}) to ({endx}, {endy})") from err
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def move(self, x: float, y: float):
|
|
137
|
+
"""
|
|
138
|
+
Moving Axi arm to the absolute position in axi usable
|
|
139
|
+
area. where x and y are inches.
|
|
140
|
+
"""
|
|
141
|
+
try:
|
|
142
|
+
if x == 0 and y == 0:
|
|
143
|
+
self.axi.home()
|
|
144
|
+
return
|
|
145
|
+
self.axi.move(x, y)
|
|
146
|
+
except Exception as err:
|
|
147
|
+
raise Error(f"Failed to Move pointing device from ({x}, {y})") from err
|
|
148
|
+
|
|
149
|
+
def home(self):
|
|
150
|
+
"""Returns the touch to its home default position."""
|
|
151
|
+
try:
|
|
152
|
+
self.axi.home_with_limit_switch()
|
|
153
|
+
except Exception as err:
|
|
154
|
+
raise Error(f"Failed to Home pointing device") from err
|
|
155
|
+
|
|
156
|
+
def touch_command(self, cmd, *args):
|
|
157
|
+
"""Executes a backend-specific touch command on the given device controller."""
|
|
158
|
+
cmd = cmd.lower()
|
|
159
|
+
if cmd == "move":
|
|
160
|
+
if len(args) != 2:
|
|
161
|
+
raise Error("move command expects x and y as argument")
|
|
162
|
+
x = float(args[0])
|
|
163
|
+
y = float(args[1])
|
|
164
|
+
self.move(x, y)
|
|
165
|
+
elif cmd == "home":
|
|
166
|
+
self.home()
|
|
167
|
+
|
|
168
|
+
else:
|
|
169
|
+
raise Error(f"Invalid command {cmd}")
|
|
170
|
+
|
|
171
|
+
def set_param(self, param: str, value: bool) -> None:
|
|
172
|
+
"""
|
|
173
|
+
Set a runtime parameter for ArucoCapture.
|
|
174
|
+
|
|
175
|
+
| *Arguments* | *Description* |
|
|
176
|
+
| `param (str)` | Supported: `auto_home`. |
|
|
177
|
+
| `value (bool)` | True or False Value Supported. |
|
|
178
|
+
|
|
179
|
+
*Raises:*
|
|
180
|
+
| `Error` | If an unsupported parameter is provided. |
|
|
181
|
+
"""
|
|
182
|
+
if param == 'auto_home' and param in self.PROPERTIES:
|
|
183
|
+
self.auto_home = value
|
|
184
|
+
else:
|
|
185
|
+
raise Error(f"ArucoCapture Error: Unsupported parameter: {param}")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
class Error(Exception):
|
|
4
|
+
"""Base error for all TouchController implementations."""
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
class BaseTouch(ABC):
|
|
8
|
+
@abstractmethod
|
|
9
|
+
def touch_init(self) -> None:
|
|
10
|
+
"""Initialize the touch controller."""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def touch_tap(self, x: float, y: float, tap_time) -> None:
|
|
15
|
+
"""Simulate a tap at (x, y) in pixels for the given duration."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def touch_swipe(
|
|
20
|
+
self,
|
|
21
|
+
startx: float,
|
|
22
|
+
starty: float,
|
|
23
|
+
endx: float,
|
|
24
|
+
endy: float,
|
|
25
|
+
tap_time
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Simulate a swipe from (startx, starty) to (endx, endy) for the given duration."""
|
|
28
|
+
pass
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def touch_command(self, cmd, *args) -> None:
|
|
31
|
+
"""Executes a backend-specific touch command on the given device controller."""
|
|
32
|
+
pass
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from parrot.touch.base import BaseTouch
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
class DummyTouch(BaseTouch):
|
|
5
|
+
"""A dummy touch controller for testing purposes.
|
|
6
|
+
|
|
7
|
+
This class simulates touch interactions by printing output
|
|
8
|
+
instead of interacting with physical hardware.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, config: Any = None) -> None:
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
def touch_init(self) -> None:
|
|
15
|
+
"""Initialize the dummy touch system."""
|
|
16
|
+
print("DummyTouch: Initialized")
|
|
17
|
+
|
|
18
|
+
def touch_tap(self, x: float, y: float, tap_time) -> None:
|
|
19
|
+
"""Simulate a tap gesture at the given coordinates."""
|
|
20
|
+
print(f"DummyTouch: Tap at ({x}, {y}) for {tap_time}s")
|
|
21
|
+
|
|
22
|
+
def touch_swipe(
|
|
23
|
+
self,
|
|
24
|
+
startx: float,
|
|
25
|
+
starty: float,
|
|
26
|
+
endx: float,
|
|
27
|
+
endy: float,
|
|
28
|
+
tap_time
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Simulate a swipe gesture from start to end coordinates."""
|
|
31
|
+
print(f"DummyTouch: Swipe from ({startx}, {starty}) to ({endx}, {endy}) for {tap_time}s")
|
|
32
|
+
|
|
33
|
+
def touch_command(self, cmd, *args) -> None:
|
|
34
|
+
"""Executes a backend-specific touch command on the given device controller."""
|
|
35
|
+
print(f"DummyTouch: Touch command {cmd}, {args}")
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import xmlrpc.client
|
|
2
|
+
from typing import Any, Tuple
|
|
3
|
+
from parrot.touch.base import BaseTouch, Error
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class UsbHidTouch(BaseTouch):
|
|
7
|
+
"""
|
|
8
|
+
A touch controller implementation using USB-HID over XML-RPC.
|
|
9
|
+
|
|
10
|
+
This class allows simulating tap and swipe gestures by sending touch commands
|
|
11
|
+
over the network to a USB-HID device capable of interpreting XML-RPC messages.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, config: dict[str, Any]) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Initializes the USB-HID touch controller.
|
|
17
|
+
|
|
18
|
+
The configuration dictionary must contain:
|
|
19
|
+
- `host`: IP or hostname of the XML-RPC server.
|
|
20
|
+
- `port`: Port number of the server.
|
|
21
|
+
- `width`: Screen width in pixels.
|
|
22
|
+
- `height`: Screen height in pixels.
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
Error: If any required configuration is missing or initialization fails.
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
self.host: str = config["host"]
|
|
29
|
+
self.port: int = config["port"]
|
|
30
|
+
self.screen_width_px: int = config["width"]
|
|
31
|
+
self.screen_height_px: int = config["height"]
|
|
32
|
+
self.handler = None
|
|
33
|
+
except KeyError as err:
|
|
34
|
+
raise Error(f"Missing required configuration key: {err}") from err
|
|
35
|
+
except Exception as err:
|
|
36
|
+
raise Error(f"Initialization failed: {err}") from err
|
|
37
|
+
|
|
38
|
+
def touch_init(self) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Initializes the XML-RPC connection to the USB-HID server.
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
Error: If the connection to the HID server fails.
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
self.handler = xmlrpc.client.ServerProxy(f"http://{self.host}:{self.port}/")
|
|
47
|
+
print(f"UsbHidTouch: Connected to {self.host}:{self.port}")
|
|
48
|
+
except Exception as err:
|
|
49
|
+
raise Error(f"Failed to initialize USB-HID connection: {err}") from err
|
|
50
|
+
|
|
51
|
+
def _convert_to_hid_coordinates(self, x: float, y: float) -> Tuple[float, float]:
|
|
52
|
+
"""
|
|
53
|
+
Converts pixel coordinates to USB-HID scaled coordinates (0 to 0.5).
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
x (float): X-coordinate in pixels.
|
|
57
|
+
y (float): Y-coordinate in pixels.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Tuple[float, float]: Normalized HID coordinates.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
Error: If the conversion fails.
|
|
64
|
+
"""
|
|
65
|
+
try:
|
|
66
|
+
x_point = (x / self.screen_width_px) * 0.5
|
|
67
|
+
y_point = (y / self.screen_height_px) * 0.5
|
|
68
|
+
return round(x_point, 6), round(y_point, 6)
|
|
69
|
+
except Exception as err:
|
|
70
|
+
raise Error(f"Failed to convert coordinates ({x}, {y})") from err
|
|
71
|
+
|
|
72
|
+
def touch_tap(self, x: float, y: float, tap_time) -> None:
|
|
73
|
+
"""
|
|
74
|
+
Simulates a tap gesture at the given screen coordinates.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
x (float): X-coordinate in pixels.
|
|
78
|
+
y (float): Y-coordinate in pixels.
|
|
79
|
+
tap_time: Duration to hold touch (passed for consistency, but used by remote endpoint).
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
Error: If the tap operation fails or response is invalid.
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
hx, hy = self._convert_to_hid_coordinates(x, y)
|
|
86
|
+
command = f"T,{hx},{hy},{hx},{hy},{tap_time}"
|
|
87
|
+
response = self.handler.process_data(command.encode()).data.decode().strip()
|
|
88
|
+
if response != 'EXECUTED':
|
|
89
|
+
raise Error(f"Unexpected response from HID device: {response}")
|
|
90
|
+
print(f"UsbHidTouch: Tap at ({x}, {y}) with response '{response}'")
|
|
91
|
+
except Exception as err:
|
|
92
|
+
raise Error(f"Touch tap failed at ({x}, {y})") from err
|
|
93
|
+
|
|
94
|
+
def touch_swipe(self, startx: float, starty: float, endx: float, endy: float, tap_time) -> None:
|
|
95
|
+
"""
|
|
96
|
+
Simulates a swipe gesture from start to end coordinates.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
startx (float): Start X-coordinate in pixels.
|
|
100
|
+
starty (float): Start Y-coordinate in pixels.
|
|
101
|
+
endx (float): End X-coordinate in pixels.
|
|
102
|
+
endy (float): End Y-coordinate in pixels.
|
|
103
|
+
tap_time: Duration of swipe (passed for consistency, but used by remote endpoint).
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
Error: If the swipe operation fails or response is invalid.
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
sx, sy = self._convert_to_hid_coordinates(startx, starty)
|
|
110
|
+
ex, ey = self._convert_to_hid_coordinates(endx, endy)
|
|
111
|
+
command = f"T,{sx},{sy},{ex},{ey},{tap_time}"
|
|
112
|
+
response = self.handler.process_data(command.encode()).data.decode().strip()
|
|
113
|
+
if response != 'EXECUTED':
|
|
114
|
+
raise Error(f"Unexpected response from HID device: {response}")
|
|
115
|
+
print(f"UsbHidTouch: Swipe from ({startx}, {starty}) to ({endx}, {endy}) with response '{response}'")
|
|
116
|
+
except Exception as err:
|
|
117
|
+
raise Error(f"Swipe failed from ({startx}, {starty}) to ({endx}, {endy})") from err
|
|
118
|
+
|
|
119
|
+
def touch_command(self, cmd, *args):
|
|
120
|
+
raise Error("Commands are not supported by the UsbHidTouch backend")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "robotframework-parrot-touchcontroller"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Touch control api to use within the Robot Framework ecosystem"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Zilogic Systems", email = "code@zilogic.com" },
|
|
11
|
+
]
|
|
12
|
+
requires-python = ">=3.6"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
license = "Apache-2.0"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"robotframework",
|
|
17
|
+
"pyyaml",
|
|
18
|
+
"robotframework-parrot-adbcontroller"
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://github.com/zilogic-systems/parrot"
|
|
24
|
+
#Documentation = "https://readthedocs.org
|
|
25
|
+
Repository = "https://github.com//zilogic-systems/parrot.git"
|
|
26
|
+
Issues = "https://github.com/zilogic-systems/parrot/issues"
|
|
27
|
+
#Changelog = "https://github.com/zilogic-systems/parrot//blob/master/CHANGELOG.md"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
where = ["."]
|
|
32
|
+
include = ["parrot*"]
|
robotframework_parrot_touchcontroller-1.0.0/robotframework_parrot_touchcontroller.egg-info/PKG-INFO
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: robotframework-parrot-touchcontroller
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Touch control api to use within the Robot Framework ecosystem
|
|
5
|
+
Author-email: Zilogic Systems <code@zilogic.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/zilogic-systems/parrot
|
|
8
|
+
Project-URL: Repository, https://github.com//zilogic-systems/parrot.git
|
|
9
|
+
Project-URL: Issues, https://github.com/zilogic-systems/parrot/issues
|
|
10
|
+
Requires-Python: >=3.6
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: robotframework
|
|
13
|
+
Requires-Dist: pyyaml
|
|
14
|
+
Requires-Dist: robotframework-parrot-adbcontroller
|
|
15
|
+
|
|
16
|
+
# Touch Controller
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
To install from package registry use the following command:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
$ pip install robotframework-parrot-touchcontroller
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
To install from git repo use the following command:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
$ pip install .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Example
|
|
33
|
+
|
|
34
|
+
Example of using the library in Robot Framework is available `example.robot`.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
parrot/TouchController.py
|
|
4
|
+
parrot/touch/adbtouch.py
|
|
5
|
+
parrot/touch/axitouch.py
|
|
6
|
+
parrot/touch/base.py
|
|
7
|
+
parrot/touch/dummy.py
|
|
8
|
+
parrot/touch/usbhidtouch.py
|
|
9
|
+
robotframework_parrot_touchcontroller.egg-info/PKG-INFO
|
|
10
|
+
robotframework_parrot_touchcontroller.egg-info/SOURCES.txt
|
|
11
|
+
robotframework_parrot_touchcontroller.egg-info/dependency_links.txt
|
|
12
|
+
robotframework_parrot_touchcontroller.egg-info/requires.txt
|
|
13
|
+
robotframework_parrot_touchcontroller.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
parrot
|