robotframework-parrot-touchcontroller 1.0.0__tar.gz → 1.0.1__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 → robotframework_parrot_touchcontroller-1.0.1}/PKG-INFO +4 -4
- {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.0.1}/parrot/TouchController.py +2 -2
- robotframework_parrot_touchcontroller-1.0.1/parrot/touch/axitouch.py +343 -0
- {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.0.1}/pyproject.toml +4 -4
- {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.0.1}/robotframework_parrot_touchcontroller.egg-info/PKG-INFO +4 -4
- robotframework_parrot_touchcontroller-1.0.1/robotframework_parrot_touchcontroller.egg-info/requires.txt +3 -0
- robotframework_parrot_touchcontroller-1.0.0/parrot/touch/axitouch.py +0 -185
- robotframework_parrot_touchcontroller-1.0.0/robotframework_parrot_touchcontroller.egg-info/requires.txt +0 -3
- {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.0.1}/README.md +0 -0
- {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.0.1}/parrot/touch/adbtouch.py +0 -0
- {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.0.1}/parrot/touch/base.py +0 -0
- {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.0.1}/parrot/touch/dummy.py +0 -0
- {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.0.1}/parrot/touch/usbhidtouch.py +0 -0
- {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.0.1}/robotframework_parrot_touchcontroller.egg-info/SOURCES.txt +0 -0
- {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.0.1}/robotframework_parrot_touchcontroller.egg-info/dependency_links.txt +0 -0
- {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.0.1}/robotframework_parrot_touchcontroller.egg-info/top_level.txt +0 -0
- {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.0.1}/setup.cfg +0 -0
{robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.0.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: robotframework-parrot-touchcontroller
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Touch control api to use within the Robot Framework ecosystem
|
|
5
5
|
Author-email: Zilogic Systems <code@zilogic.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -9,9 +9,9 @@ Project-URL: Repository, https://github.com//zilogic-systems/parrot.git
|
|
|
9
9
|
Project-URL: Issues, https://github.com/zilogic-systems/parrot/issues
|
|
10
10
|
Requires-Python: >=3.6
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
|
-
Requires-Dist: robotframework
|
|
13
|
-
Requires-Dist: pyyaml
|
|
14
|
-
Requires-Dist: robotframework-parrot-adbcontroller
|
|
12
|
+
Requires-Dist: robotframework~=7.3.2
|
|
13
|
+
Requires-Dist: pyyaml~=6.0.2
|
|
14
|
+
Requires-Dist: robotframework-parrot-adbcontroller~=1.0.0
|
|
15
15
|
|
|
16
16
|
# Touch Controller
|
|
17
17
|
|
|
@@ -144,11 +144,11 @@ class TouchController:
|
|
|
144
144
|
print(f"[INFO] Loaded controllers: {list(self.devices.keys())}")
|
|
145
145
|
|
|
146
146
|
@keyword("Tap")
|
|
147
|
-
def send_touch(self, device: str, x: float, y: float, tap_time: float
|
|
147
|
+
def send_touch(self, device: str, x: float, y: float, tap_time: float) -> None:
|
|
148
148
|
"""
|
|
149
149
|
Simulates a tap action on the given device at specified screen coordinates.
|
|
150
150
|
Requires the device name, X and Y coordinates in pixels, and an
|
|
151
|
-
|
|
151
|
+
tap duration.
|
|
152
152
|
|
|
153
153
|
*Syntax*
|
|
154
154
|
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# pylint: disable=no-member
|
|
2
|
+
|
|
3
|
+
from robot.api.deco import keyword
|
|
4
|
+
from parrot.touch.base import BaseTouch,Error
|
|
5
|
+
from typing import Tuple, Any
|
|
6
|
+
import yaml
|
|
7
|
+
import os
|
|
8
|
+
import tkinter as tk
|
|
9
|
+
from tkinter import messagebox
|
|
10
|
+
|
|
11
|
+
import xmlrpc.client
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AxiTouchError(Exception):
|
|
15
|
+
"""Represents an error in the AxiDrawTouch controller."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AxiDrawTouch(BaseTouch):
|
|
19
|
+
"""A touch controller implementation using the AxiDraw plotting device.
|
|
20
|
+
|
|
21
|
+
This class provides methods to simulate touch gestures such as tap and swipe
|
|
22
|
+
by controlling an AxiDraw plotter based on screen coordinates.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
PROPERTIES = ["auto_home"]
|
|
26
|
+
|
|
27
|
+
def __init__(self, config: dict[str, Any]) -> None:
|
|
28
|
+
"""Initializes the AxiDraw touch controller.
|
|
29
|
+
|
|
30
|
+
The configuration dictionary must contain:
|
|
31
|
+
- `width`: screen width in pixels
|
|
32
|
+
- `height`: screen height in pixels
|
|
33
|
+
- `start_pos`: tuple (x, y) of starting cartesian coordinates (in inches)
|
|
34
|
+
- `end_pos`: tuple (x, y) of ending cartesian coordinates (in inches)
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
AxiTouchError: If initialization or connection to AxiDraw fails.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
uri = f"http://{config['host']}:{config['port']}"
|
|
42
|
+
self.screen_width_px: int = config["width"]
|
|
43
|
+
self.screen_height_px: int = config["height"]
|
|
44
|
+
self.auto_home = False # Flag to track auto-home behavior
|
|
45
|
+
self.calibration_file: str = config["calibration_data"]
|
|
46
|
+
self.device_name = config.get("device_name")
|
|
47
|
+
self.start_x_pos = None
|
|
48
|
+
self.start_y_pos = None
|
|
49
|
+
self.end_x_pos = None
|
|
50
|
+
self.end_y_pos = None
|
|
51
|
+
self.start_position = None
|
|
52
|
+
self.end_position = None
|
|
53
|
+
|
|
54
|
+
# Optional: configure pen heights and speed
|
|
55
|
+
pen_pos_up = 75
|
|
56
|
+
pen_pos_down = 40
|
|
57
|
+
speed_pendown = 100
|
|
58
|
+
speed_penup = 100
|
|
59
|
+
accel = 75
|
|
60
|
+
|
|
61
|
+
self.axi = xmlrpc.client.ServerProxy(uri)
|
|
62
|
+
|
|
63
|
+
self.axi.connect(accel,
|
|
64
|
+
pen_pos_up, pen_pos_down,
|
|
65
|
+
speed_penup, speed_pendown)
|
|
66
|
+
self._load_calibration_data()
|
|
67
|
+
|
|
68
|
+
except KeyError as err:
|
|
69
|
+
raise Error(f"Missing required configuration key: {err}") from err
|
|
70
|
+
except Exception as err:
|
|
71
|
+
raise Error(f"Initialization failed: {err}") from err
|
|
72
|
+
|
|
73
|
+
def _load_calibration_data(self):
|
|
74
|
+
"""Load start and end Cartesian positions from calibration YAML."""
|
|
75
|
+
if not os.path.exists(self.calibration_file):
|
|
76
|
+
raise FileNotFoundError(
|
|
77
|
+
f"Calibration file '{self.calibration_file}' not found. "
|
|
78
|
+
f"Please perform calibration to get the start and end positions."
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
with open(self.calibration_file, 'r') as file:
|
|
83
|
+
config = yaml.safe_load(file) or {}
|
|
84
|
+
except yaml.YAMLError as e:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f"Error reading YAML from '{self.calibration_file}': {e}"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if "start_position" not in config or "end_position" not in config:
|
|
90
|
+
raise KeyError(
|
|
91
|
+
"Missing 'start_position' or 'end_position' in calibration file. "
|
|
92
|
+
"Please perform calibration again."
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self.cartesian_start = tuple(config["start_position"])
|
|
96
|
+
self.cartesian_end = tuple(config["end_position"])
|
|
97
|
+
|
|
98
|
+
def convert_to_inches(self, x: float, y: float) -> Tuple[float, float]:
|
|
99
|
+
"""Convert screen coordinates (x, y) in pixels to Cartesian coordinates in inches.
|
|
100
|
+
|
|
101
|
+
*Args:*
|
|
102
|
+
`x` (float): X-coordinate in pixels.
|
|
103
|
+
`y` (float): Y-coordinate in pixels.
|
|
104
|
+
|
|
105
|
+
*Returns:*
|
|
106
|
+
`Tuple[float, float]`: Corresponding Cartesian coordinates (x, y) in inches.
|
|
107
|
+
|
|
108
|
+
*Raises:*
|
|
109
|
+
ValueError: If the screen coordinates are out of bounds or if calibration data is missing.
|
|
110
|
+
|
|
111
|
+
*NOTE:*
|
|
112
|
+
This method is valid only when the screen X-axis maps to the robot Y-axis and the screen Y-axis maps to the robot X-axis, as shown below.
|
|
113
|
+
|
|
114
|
+
Robot Y
|
|
115
|
+
+-----------------------------+
|
|
116
|
+
| Screen X |
|
|
117
|
+
Robot X | +-------------------+ |
|
|
118
|
+
| | | Screen Y
|
|
119
|
+
| +-------------------+ |
|
|
120
|
+
| |
|
|
121
|
+
+-----------------------------+
|
|
122
|
+
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
x = float(x)
|
|
126
|
+
y = float(y)
|
|
127
|
+
|
|
128
|
+
sx, sy = self.cartesian_start
|
|
129
|
+
ex, ey = self.cartesian_end
|
|
130
|
+
|
|
131
|
+
nx = y
|
|
132
|
+
ny = x
|
|
133
|
+
|
|
134
|
+
norm_w = self.screen_height_px # 720
|
|
135
|
+
norm_h = self.screen_width_px # 1920
|
|
136
|
+
|
|
137
|
+
x_scale = (ex - sx) / norm_w
|
|
138
|
+
y_scale = (ey - sy) / norm_h
|
|
139
|
+
|
|
140
|
+
robot_x = sx + nx * x_scale
|
|
141
|
+
robot_y = sy + ny * y_scale
|
|
142
|
+
|
|
143
|
+
return round(robot_x, 3), round(robot_y, 3)
|
|
144
|
+
|
|
145
|
+
def _validate_screen_coordinates(self, x: float, y: float) -> None:
|
|
146
|
+
"""Validate that the screen coordinates are within the configured display."""
|
|
147
|
+
|
|
148
|
+
if not (0 <= x <= self.screen_width_px):
|
|
149
|
+
raise Error(
|
|
150
|
+
f"Touch tap failed at ({x}, {y}) due to out-of-bound coordinates"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if not (0 <= y <= self.screen_height_px):
|
|
154
|
+
raise Error(
|
|
155
|
+
f"Touch tap failed at ({x}, {y}) due to out-of-bound coordinates"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def touch_init(self) -> None:
|
|
160
|
+
"""Initialize the pen by lifting it up and moving to origin (0, 0).
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
AxiTouchError: If initialization movement fails.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
self.axi.penup()
|
|
168
|
+
self.axi.home()
|
|
169
|
+
print("AxiDrawTouch: Initialized and moved to origin")
|
|
170
|
+
except Exception as err:
|
|
171
|
+
raise Error("Initialization failed") from err
|
|
172
|
+
|
|
173
|
+
def touch_tap(self, x: float, y: float, tap_time) -> None:
|
|
174
|
+
"""Simulate a tap at the given screen coordinates.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
x (int | float): X-coordinate in pixels.
|
|
178
|
+
y (int | float): Y-coordinate in pixels.
|
|
179
|
+
tap_time (float): Duration to hold pen down.
|
|
180
|
+
Raises:
|
|
181
|
+
AxiTouchError: If the tap operation fails.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
x = float(x)
|
|
186
|
+
y = float(y)
|
|
187
|
+
self._validate_screen_coordinates(x, y)
|
|
188
|
+
px, py = self.convert_to_inches(x, y)
|
|
189
|
+
print(px, py)
|
|
190
|
+
self.axi.tap(px, py, tap_time)
|
|
191
|
+
if self.auto_home:
|
|
192
|
+
self.axi.home()
|
|
193
|
+
print(f"AxiDrawTouch: Tap at ({x}, {y}) for {tap_time}s")
|
|
194
|
+
except Error:
|
|
195
|
+
raise
|
|
196
|
+
except Exception as err:
|
|
197
|
+
raise Error(f"Touch tap failed at ({x}, {y})") from err
|
|
198
|
+
|
|
199
|
+
def touch_swipe(
|
|
200
|
+
self,
|
|
201
|
+
startx: float,
|
|
202
|
+
starty: float,
|
|
203
|
+
endx: float,
|
|
204
|
+
endy: float
|
|
205
|
+
) -> None:
|
|
206
|
+
"""Simulate a swipe gesture from start to end coordinates.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
startx (int | float): Start X-coordinate in pixels.
|
|
210
|
+
starty (int | float): Start Y-coordinate in pixels.
|
|
211
|
+
endx (int | float): End X-coordinate in pixels.
|
|
212
|
+
endy (int | float): End Y-coordinate in pixels.
|
|
213
|
+
swipe_time (float): Duration of the swipe.
|
|
214
|
+
|
|
215
|
+
Raises:
|
|
216
|
+
AxiTouchError: If the swipe operation fails.
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
startx = float(startx)
|
|
221
|
+
starty = float(starty)
|
|
222
|
+
endx = float(endx)
|
|
223
|
+
endy = float(endy)
|
|
224
|
+
self._validate_screen_coordinates(startx, starty)
|
|
225
|
+
self._validate_screen_coordinates(endx, endy)
|
|
226
|
+
psx, psy = self.convert_to_inches(startx, starty)
|
|
227
|
+
pex, pey = self.convert_to_inches(endx, endy)
|
|
228
|
+
self.axi.swipe(psx, psy, pex, pey)
|
|
229
|
+
|
|
230
|
+
print(
|
|
231
|
+
f"AxiDrawTouch: Swipe from "f"({startx}, {starty}) to ({endx}, {endy})")
|
|
232
|
+
|
|
233
|
+
except Error:
|
|
234
|
+
raise
|
|
235
|
+
|
|
236
|
+
except Exception as err:
|
|
237
|
+
raise Error(
|
|
238
|
+
f"Swipe failed from "
|
|
239
|
+
f"({startx}, {starty}) to ({endx}, {endy})") from err
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def move(self, x: float, y: float):
|
|
243
|
+
"""
|
|
244
|
+
Moving Axi arm to the absolute position in axi usable
|
|
245
|
+
area. where x and y are inches.
|
|
246
|
+
"""
|
|
247
|
+
try:
|
|
248
|
+
if x == 0 and y == 0:
|
|
249
|
+
self.axi.home()
|
|
250
|
+
return
|
|
251
|
+
self.axi.move(x, y)
|
|
252
|
+
except Exception as err:
|
|
253
|
+
raise Error(f"Failed to Move pointing device from ({x}, {y})") from err
|
|
254
|
+
|
|
255
|
+
def home(self):
|
|
256
|
+
"""Returns the touch to its home default position."""
|
|
257
|
+
try:
|
|
258
|
+
self.axi.home_with_limit_switch()
|
|
259
|
+
except Exception as err:
|
|
260
|
+
raise Error(f"Failed to Home pointing device") from err
|
|
261
|
+
|
|
262
|
+
def wait_for_ok(self, message):
|
|
263
|
+
root = tk.Tk()
|
|
264
|
+
root.withdraw()
|
|
265
|
+
messagebox.showinfo("Position Calculation", message)
|
|
266
|
+
root.destroy()
|
|
267
|
+
|
|
268
|
+
def calibrate(self):
|
|
269
|
+
"""Returns the calibration for start and end position"""
|
|
270
|
+
try:
|
|
271
|
+
|
|
272
|
+
self.start_x_pos, self.start_y_pos = self._calibrate_corner(f"Move the TouchBot arm to the top-left corner of {self.device_name}. Click OK to continue.")
|
|
273
|
+
|
|
274
|
+
self.end_x_pos, self.end_y_pos = self._calibrate_corner(f"Move the TouchBot arm to the bottom-right corner of {self.device_name}. Click OK to continue.")
|
|
275
|
+
|
|
276
|
+
self.save_calibrate_position()
|
|
277
|
+
|
|
278
|
+
except Exception as err:
|
|
279
|
+
raise Error("Failed to Calibrate the device") from err
|
|
280
|
+
|
|
281
|
+
def _calibrate_corner(self, message: str):
|
|
282
|
+
self.axi.disconnect()
|
|
283
|
+
self.wait_for_ok(message)
|
|
284
|
+
self.axi.connect()
|
|
285
|
+
x, y = self.axi.calibrate_with_limit_switch()
|
|
286
|
+
return round(abs(x), 2), round(abs(y), 2)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def save_calibrate_position(self):
|
|
290
|
+
try:
|
|
291
|
+
calibrate_position = {}
|
|
292
|
+
|
|
293
|
+
if not os.path.exists(self.calibration_file):
|
|
294
|
+
pass
|
|
295
|
+
else:
|
|
296
|
+
with open(self.calibration_file, "r") as file:
|
|
297
|
+
loaded_file = yaml.safe_load(file)
|
|
298
|
+
if isinstance(loaded_file, dict):
|
|
299
|
+
calibrate_position = loaded_file
|
|
300
|
+
|
|
301
|
+
calibrate_position["start_position"] = [self.start_x_pos, self.start_y_pos]
|
|
302
|
+
calibrate_position["end_position"] = [self.end_x_pos, self.end_y_pos]
|
|
303
|
+
|
|
304
|
+
with open(self.calibration_file, "w") as file:
|
|
305
|
+
yaml.safe_dump(calibrate_position, file, sort_keys=False)
|
|
306
|
+
|
|
307
|
+
print("YAML file updated successfully.")
|
|
308
|
+
|
|
309
|
+
except Exception as err:
|
|
310
|
+
raise Error(f"Error saving ArUco points: {err}") from err
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def touch_command(self, cmd, *args):
|
|
314
|
+
"""Executes a backend-specific touch command on the given device controller."""
|
|
315
|
+
cmd = cmd.lower()
|
|
316
|
+
if cmd == "move":
|
|
317
|
+
if len(args) != 2:
|
|
318
|
+
raise Error("move command expects x and y as argument")
|
|
319
|
+
x = float(args[0])
|
|
320
|
+
y = float(args[1])
|
|
321
|
+
self.move(x, y)
|
|
322
|
+
elif cmd == "home":
|
|
323
|
+
self.home()
|
|
324
|
+
elif cmd == "calibrate":
|
|
325
|
+
self.calibrate()
|
|
326
|
+
else:
|
|
327
|
+
raise Error(f"Invalid command {cmd}")
|
|
328
|
+
|
|
329
|
+
def set_param(self, param: str, value: bool) -> None:
|
|
330
|
+
"""
|
|
331
|
+
Set a runtime parameter for ArucoCapture.
|
|
332
|
+
|
|
333
|
+
| *Arguments* | *Description* |
|
|
334
|
+
| `param (str)` | Supported: `auto_home`. |
|
|
335
|
+
| `value (bool)` | True or False Value Supported. |
|
|
336
|
+
|
|
337
|
+
*Raises:*
|
|
338
|
+
| `Error` | If an unsupported parameter is provided. |
|
|
339
|
+
"""
|
|
340
|
+
if param == 'auto_home' and param in self.PROPERTIES:
|
|
341
|
+
self.auto_home = value
|
|
342
|
+
else:
|
|
343
|
+
raise Error(f"ArucoCapture Error: Unsupported parameter: {param}")
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "robotframework-parrot-touchcontroller"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.1"
|
|
8
8
|
description = "Touch control api to use within the Robot Framework ecosystem"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Zilogic Systems", email = "code@zilogic.com" },
|
|
@@ -13,9 +13,9 @@ requires-python = ">=3.6"
|
|
|
13
13
|
readme = "README.md"
|
|
14
14
|
license = "Apache-2.0"
|
|
15
15
|
dependencies = [
|
|
16
|
-
"robotframework",
|
|
17
|
-
"pyyaml",
|
|
18
|
-
"robotframework-parrot-adbcontroller"
|
|
16
|
+
"robotframework~=7.3.2",
|
|
17
|
+
"pyyaml~=6.0.2",
|
|
18
|
+
"robotframework-parrot-adbcontroller~=1.0.0"
|
|
19
19
|
]
|
|
20
20
|
|
|
21
21
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: robotframework-parrot-touchcontroller
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Touch control api to use within the Robot Framework ecosystem
|
|
5
5
|
Author-email: Zilogic Systems <code@zilogic.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -9,9 +9,9 @@ Project-URL: Repository, https://github.com//zilogic-systems/parrot.git
|
|
|
9
9
|
Project-URL: Issues, https://github.com/zilogic-systems/parrot/issues
|
|
10
10
|
Requires-Python: >=3.6
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
|
-
Requires-Dist: robotframework
|
|
13
|
-
Requires-Dist: pyyaml
|
|
14
|
-
Requires-Dist: robotframework-parrot-adbcontroller
|
|
12
|
+
Requires-Dist: robotframework~=7.3.2
|
|
13
|
+
Requires-Dist: pyyaml~=6.0.2
|
|
14
|
+
Requires-Dist: robotframework-parrot-adbcontroller~=1.0.0
|
|
15
15
|
|
|
16
16
|
# Touch Controller
|
|
17
17
|
|
|
@@ -1,185 +0,0 @@
|
|
|
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}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|