robotframework-parrot-touchcontroller 1.0.0__tar.gz → 1.1.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.
Files changed (18) hide show
  1. {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.1.0}/PKG-INFO +4 -4
  2. {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.1.0}/parrot/TouchController.py +15 -16
  3. {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.1.0}/parrot/touch/adbtouch.py +5 -18
  4. robotframework_parrot_touchcontroller-1.1.0/parrot/touch/axitouch.py +343 -0
  5. robotframework_parrot_touchcontroller-1.1.0/parrot/touch/axitouchserial.py +1147 -0
  6. {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.1.0}/pyproject.toml +4 -4
  7. {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.1.0}/robotframework_parrot_touchcontroller.egg-info/PKG-INFO +4 -4
  8. {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.1.0}/robotframework_parrot_touchcontroller.egg-info/SOURCES.txt +1 -0
  9. robotframework_parrot_touchcontroller-1.1.0/robotframework_parrot_touchcontroller.egg-info/requires.txt +3 -0
  10. robotframework_parrot_touchcontroller-1.0.0/parrot/touch/axitouch.py +0 -185
  11. robotframework_parrot_touchcontroller-1.0.0/robotframework_parrot_touchcontroller.egg-info/requires.txt +0 -3
  12. {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.1.0}/README.md +0 -0
  13. {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.1.0}/parrot/touch/base.py +0 -0
  14. {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.1.0}/parrot/touch/dummy.py +0 -0
  15. {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.1.0}/parrot/touch/usbhidtouch.py +0 -0
  16. {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.1.0}/robotframework_parrot_touchcontroller.egg-info/dependency_links.txt +0 -0
  17. {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.1.0}/robotframework_parrot_touchcontroller.egg-info/top_level.txt +0 -0
  18. {robotframework_parrot_touchcontroller-1.0.0 → robotframework_parrot_touchcontroller-1.1.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robotframework-parrot-touchcontroller
3
- Version: 1.0.0
3
+ Version: 1.1.0
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
 
@@ -17,9 +17,9 @@ class TouchController:
17
17
 
18
18
  == Configuration ==
19
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
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
23
  a `type` field that specifies the device type. Supported types include:
24
24
 
25
25
  - `UsbHidTouch`: USB HID-based touch simulation over TCP.
@@ -44,7 +44,7 @@ class TouchController:
44
44
  === ADBTouch ===
45
45
  Simulates touch events on an Android device using ADB commands.
46
46
  Supported configs:
47
- - `ip`: IP address of the Android device (e.g., `192.168.43.168`)
47
+ - `type`: ADBTouch
48
48
 
49
49
  === Configuration Sample (config.yaml) ===
50
50
 
@@ -69,12 +69,11 @@ class TouchController:
69
69
  |
70
70
  | T3:
71
71
  | type: ADBTouch
72
- | ip: 192.168.43.168
73
72
 
74
73
  = General Usage =
75
74
 
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,
75
+ Setup and initialize touch devices from a YAML configuration. Simulate touch input such as tap and swipe gestures
76
+ across multiple supported devices. This library supports both real physical systems and simulated touch environments,
78
77
  making it suitable for automated device interaction and UI testing.
79
78
 
80
79
  = Example =
@@ -102,7 +101,7 @@ class TouchController:
102
101
  @keyword("Touch Setup")
103
102
  def setup(self, config: Dict[str, Dict[str, Any]]) -> None:
104
103
  """Initializes and configures touch controller devices.
105
- Loads controller instances from the given `Configuration` dictionary
104
+ Loads controller instances from the given `Configuration` dictionary
106
105
  and prepares them for input simulation.
107
106
 
108
107
  *Syntax*
@@ -144,11 +143,11 @@ class TouchController:
144
143
  print(f"[INFO] Loaded controllers: {list(self.devices.keys())}")
145
144
 
146
145
  @keyword("Tap")
147
- def send_touch(self, device: str, x: float, y: float, tap_time: float = 0.2) -> None:
146
+ def send_touch(self, device: str, x: float, y: float, tap_time: float) -> None:
148
147
  """
149
148
  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).
149
+ Requires the device name, X and Y coordinates in pixels, and an
150
+ tap duration.
152
151
 
153
152
  *Syntax*
154
153
 
@@ -197,11 +196,11 @@ class TouchController:
197
196
  """
198
197
  Executes a backend-specific touch command on the given device controller.
199
198
 
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
199
+ This keyword is used to send a command (e.g., `Move`, `Home`) to a specific device.
200
+ The available commands and required arguments depends on the backend implementation
202
201
  of the touch controller. Currently only the AxiDrawTouch backend has commands.
203
202
 
204
- AxiDrawTouch backend supports the following commands.
203
+ AxiDrawTouch backend supports the following commands.
205
204
 
206
205
  - `Move` — Moves to the specified position (in inches) without touching.
207
206
  Requires two arguments: `x`, `y`.
@@ -228,12 +227,12 @@ class TouchController:
228
227
  def set_param_auto_home(self, device: str, param: str, value: str) -> None:
229
228
  """
230
229
  Sets values to configure runtime parameters for Touch control devices.
231
-
230
+
232
231
  Currently, this keyword supports only for `AxiDrawTouch` type devices,
233
232
 
234
233
  | *Parameter* | *Description* | *Example Value* |
235
234
  | `auto_home` | Specifies the touch action to be directed to the home position | "True or False" |
236
-
235
+
237
236
  *Syntax*
238
237
 
239
238
  | `Touch Set Param` | <DEVICE_NAME> | auto_home | <VALUE> |
@@ -1,4 +1,4 @@
1
- from parrot import ADBController
1
+ from robot.libraries.BuiltIn import BuiltIn
2
2
  from parrot.touch.base import BaseTouch, Error
3
3
 
4
4
  class ADBTouch(BaseTouch):
@@ -12,20 +12,8 @@ class ADBTouch(BaseTouch):
12
12
  def __init__(self, config: dict):
13
13
  """
14
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
15
  """
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
16
+ self.adb = None
29
17
 
30
18
  def touch_init(self):
31
19
  """
@@ -35,10 +23,9 @@ class ADBTouch(BaseTouch):
35
23
  Error: If the device connection fails.
36
24
  """
37
25
  try:
38
- self.adb.adb_ip_connect(self.device_ip)
39
- print(f"ADBTouch: Connected to device {self.device_ip}")
26
+ self.adb = BuiltIn().get_library_instance("parrot.ADBController")
40
27
  except Exception as err:
41
- raise Error("Initialization failed") from err
28
+ raise Error(f"ADB Touch Initialization failed: {err}") from err
42
29
 
43
30
  def touch_tap(self, x, y, tap_time):
44
31
  """
@@ -79,6 +66,6 @@ class ADBTouch(BaseTouch):
79
66
  print(f"ADBTouch: Swipe from ({startx}, {starty}) to ({endx}, {endy})")
80
67
  except Exception as err:
81
68
  raise Error(f"Swipe failed from ({startx}, {starty}) to ({endx}, {endy})") from err
82
-
69
+
83
70
  def touch_command(self, cmd, *args):
84
71
  raise Error("Commands are not supported by the ADBTouch backend")
@@ -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}")