urkit 0.1.0__py3-none-any.whl

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.
urkit/io.py ADDED
@@ -0,0 +1,311 @@
1
+ """Digital I/O operations for Universal Robots e-Series.
2
+
3
+ Reads and writes digital I/O signals via the RTDE IO and
4
+ receive interfaces. All pins are addressed by their hardware
5
+ number (0–17) — no separate "standard" vs "configurable" methods.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ from typing import TYPE_CHECKING, Union
12
+
13
+ import time
14
+ from urkit.exceptions import URKitIOError as IOError
15
+
16
+ if TYPE_CHECKING:
17
+ from rtde_io import RTDEIOInterface
18
+ from rtde_receive import RTDEReceiveInterface
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class IO:
24
+ """Digital I/O operations via RTDE.
25
+
26
+ Wraps the RTDE interfaces with typed, documented accessors for
27
+ digital I/O signals. All methods raise IOError on failure.
28
+
29
+ Pins are addressed by their hardware number:
30
+ - 0–7: standard digital I/O
31
+ - 8–15: configurable digital I/O
32
+ - 16–17: tool digital I/O
33
+
34
+ Args:
35
+ rtde_io: RTDEIOInterface instance (for setting outputs).
36
+ rtde_receive: RTDEReceiveInterface instance (for reading inputs/outputs).
37
+
38
+ Example:
39
+ >>> io = IO(rtde_io, rtde_r)
40
+ >>> io.set_digital_output(0, True)
41
+ >>> io.set_digital_outputs({0: True, 1: False, 8: True})
42
+ >>> value = io.get_digital_output(0)
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ rtde_io: "RTDEIOInterface",
48
+ rtde_receive: "RTDEReceiveInterface",
49
+ ) -> None:
50
+ self._rtde_io = rtde_io
51
+ self._rtde_r = rtde_receive
52
+
53
+ # ── Digital Outputs ────────────────────────────────────────────
54
+
55
+ def set_digital_output(self, pin: int, value: bool) -> None:
56
+ """Set a digital output pin.
57
+
58
+ Args:
59
+ pin: Digital output pin index (0–15 on e-Series).
60
+ Pins 0–7 are standard, 8–15 are configurable.
61
+ value: True to set high, False to set low.
62
+
63
+ Raises:
64
+ IOError: If the pin is invalid or the operation fails.
65
+ """
66
+ if not isinstance(value, bool):
67
+ raise IOError(
68
+ f"Digital output value must be bool, got {type(value).__name__}."
69
+ )
70
+ if not 0 <= pin <= 15:
71
+ raise IOError(
72
+ f"Digital output pin must be 0–15, got {pin}. "
73
+ "Pins 0–7 are standard, 8–15 are configurable."
74
+ )
75
+ try:
76
+ if pin < 8:
77
+ self._rtde_io.setStandardDigitalOut(pin, value)
78
+ else:
79
+ self._rtde_io.setConfigurableDigitalOut(pin - 8, value)
80
+ except Exception as e:
81
+ raise IOError(
82
+ f"Failed to set digital output {pin} to {value}: {e}"
83
+ )
84
+
85
+ def set_digital_outputs(
86
+ self, values: Union[bool, dict[int, bool]]
87
+ ) -> None:
88
+ """Set multiple digital outputs at once.
89
+
90
+ Pass a dict of ``{pin: value}`` pairs, or a single bool
91
+ to set all pins 0–15 to the same value.
92
+
93
+ Args:
94
+ values: Dict mapping pin numbers to bool values,
95
+ or a single bool to apply to all pins.
96
+
97
+ Raises:
98
+ IOError: If any pin is invalid or the operation fails.
99
+
100
+ Example:
101
+ >>> # Set specific pins
102
+ >>> io.set_digital_outputs({0: True, 1: False, 8: True})
103
+ >>> # Clear all outputs
104
+ >>> io.set_digital_outputs(False)
105
+ """
106
+ if isinstance(values, bool):
107
+ pins = {p: values for p in range(16)}
108
+ else:
109
+ pins = dict(values)
110
+
111
+ for pin, value in pins.items():
112
+ self.set_digital_output(pin, value)
113
+
114
+ def get_digital_output(self, pin: int) -> bool:
115
+ """Get the current state of a digital output.
116
+
117
+ Args:
118
+ pin: Digital output pin index (0–17 on e-Series).
119
+ Pins 0–7 are standard, 8–15 configurable, 16–17 tool.
120
+
121
+ Returns:
122
+ True if the output is high, False if low.
123
+
124
+ Raises:
125
+ IOError: If the pin is invalid or the read fails.
126
+ """
127
+ if not 0 <= pin <= 17:
128
+ raise IOError(
129
+ f"Digital output pin must be 0–17, got {pin}."
130
+ )
131
+ try:
132
+ return bool(self._rtde_r.getDigitalOutState(pin))
133
+ except Exception as e:
134
+ raise IOError(
135
+ f"Failed to read digital output {pin}: {e}"
136
+ )
137
+
138
+ # ── Digital Inputs ─────────────────────────────────────────────
139
+
140
+ def get_digital_input(self, pin: int) -> bool:
141
+ """Get the current state of a digital input.
142
+
143
+ Args:
144
+ pin: Digital input pin index (0–17 on e-Series).
145
+ Pins 0–7 are standard, 8–15 configurable, 16–17 tool.
146
+
147
+ Returns:
148
+ True if high, False if low.
149
+
150
+ Raises:
151
+ IOError: If the pin is invalid or the read fails.
152
+ """
153
+ if not 0 <= pin <= 17:
154
+ raise IOError(
155
+ f"Digital input pin must be 0–17, got {pin}."
156
+ )
157
+ try:
158
+ return bool(self._rtde_r.getDigitalInState(pin))
159
+ except Exception as e:
160
+ raise IOError(
161
+ f"Failed to read digital input {pin}: {e}"
162
+ )
163
+
164
+ def get_tool_input(self, pin: int) -> bool:
165
+ """Get the current state of a tool digital input.
166
+
167
+ Tool inputs correspond to pins 16–17 on the robot's I/O board
168
+ (the tool connector). Use pin 0 or 1 to access them.
169
+
170
+ Args:
171
+ pin: Tool input pin index (0–1).
172
+
173
+ Returns:
174
+ True if high, False if low.
175
+
176
+ Raises:
177
+ IOError: If the pin is invalid or the read fails.
178
+ """
179
+ if not 0 <= pin <= 1:
180
+ raise IOError(f"Tool input pin must be 0–1, got {pin}.")
181
+ try:
182
+ return bool(self._rtde_r.getDigitalInState(16 + pin))
183
+ except Exception as e:
184
+ raise IOError(
185
+ f"Failed to read tool digital input {pin}: {e}"
186
+ )
187
+
188
+ def get_tool_output(self, pin: int) -> bool:
189
+ """Get the current state of a tool digital output.
190
+
191
+ Tool outputs correspond to pins 16–17 on the robot's I/O board.
192
+ Tool outputs are configured in the robot's I/O mapping and cannot
193
+ be set directly via RTDE — they are controlled by the robot
194
+ controller based on the configured mode (auto/manual). This method
195
+ reads the actual output state.
196
+
197
+ Args:
198
+ pin: Tool output pin index (0–1).
199
+
200
+ Returns:
201
+ True if high, False if low.
202
+
203
+ Raises:
204
+ IOError: If the pin is invalid or the read fails.
205
+ """
206
+ if not 0 <= pin <= 1:
207
+ raise IOError(f"Tool output pin must be 0–1, got {pin}.")
208
+ try:
209
+ return bool(self._rtde_r.getDigitalOutState(16 + pin))
210
+ except Exception as e:
211
+ raise IOError(
212
+ f"Failed to read tool digital output {pin}: {e}"
213
+ )
214
+
215
+ # ── Analog ─────────────────────────────────────────────────────
216
+
217
+ def get_analog_input(self, pin: int) -> float:
218
+ """Get the current value of a standard analog input.
219
+
220
+ Reads from the robot's analog input channel (0–10V or 4–20mA
221
+ depending on the robot's configuration).
222
+
223
+ Args:
224
+ pin: Analog input pin index (0–1).
225
+
226
+ Returns:
227
+ Analog value in volts or amperes.
228
+
229
+ Raises:
230
+ IOError: If the pin is invalid or the read fails.
231
+ """
232
+ if pin == 0:
233
+ read_fn = self._rtde_r.getStandardAnalogInput0
234
+ elif pin == 1:
235
+ read_fn = self._rtde_r.getStandardAnalogInput1
236
+ else:
237
+ raise IOError(f"Analog input pin must be 0–1, got {pin}.")
238
+ try:
239
+ return float(read_fn())
240
+ except Exception as e:
241
+ raise IOError(
242
+ f"Failed to read analog input {pin}: {e}"
243
+ )
244
+
245
+ def get_analog_output(self, pin: int) -> float:
246
+ """Get the current value of a standard analog output.
247
+
248
+ Args:
249
+ pin: Analog output pin index (0–1).
250
+
251
+ Returns:
252
+ Analog value in volts or amperes.
253
+
254
+ Raises:
255
+ IOError: If the pin is invalid or the read fails.
256
+ """
257
+ if pin == 0:
258
+ read_fn = self._rtde_r.getStandardAnalogOutput0
259
+ elif pin == 1:
260
+ read_fn = self._rtde_r.getStandardAnalogOutput1
261
+ else:
262
+ raise IOError(f"Analog output pin must be 0–1, got {pin}.")
263
+ try:
264
+ return float(read_fn())
265
+ except Exception as e:
266
+ raise IOError(
267
+ f"Failed to read analog output {pin}: {e}"
268
+ )
269
+
270
+ # ── Wait ───────────────────────────────────────────────────────
271
+
272
+ def wait_for_input(
273
+ self,
274
+ pin: int,
275
+ value: bool = True,
276
+ *,
277
+ timeout: float = 10.0,
278
+ ) -> bool:
279
+ """Block until a digital input reaches the desired value.
280
+
281
+ Polls the specified digital input at ~50 Hz until it matches
282
+ *value* or *timeout* seconds have elapsed.
283
+
284
+ Args:
285
+ pin: Digital input pin index (0–17).
286
+ value: Desired value (True for high, False for low).
287
+ timeout: Maximum wait time in seconds (default 10.0).
288
+
289
+ Returns:
290
+ True if the input reached the desired value, False if timed out.
291
+
292
+ Raises:
293
+ IOError: If the pin is invalid or the read fails.
294
+
295
+ Example:
296
+ >>> # Wait for a limit switch on pin 0
297
+ >>> if not io.wait_for_input(0, True, timeout=5.0):
298
+ ... raise TimeoutError("Limit switch not triggered")
299
+ """
300
+ if not 0 <= pin <= 17:
301
+ raise IOError(f"Digital input pin must be 0–17, got {pin}.")
302
+
303
+ deadline = time.monotonic() + timeout
304
+ while time.monotonic() < deadline:
305
+ try:
306
+ if bool(self._rtde_r.getDigitalInState(pin)) == value:
307
+ return True
308
+ except Exception as e:
309
+ raise IOError(f"Failed to read input pin {pin}: {e}")
310
+ time.sleep(0.02) # ~50 Hz polling
311
+ return False