hid-usb-relay 25.0.0__py3-none-any.whl → 26.0.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.
@@ -1,197 +1,582 @@
1
1
  """
2
- This module provides functions to interact with a USB relay device using command-line commands.
3
- It allows users to get and set the state of the relay device, as well as retrieve information about the relay device's status.
2
+ HID USB Relay Controller Module
3
+
4
+ Provides an object-oriented interface for controlling HID USB relay devices
5
+ via command-line executable. Supports multiple relay devices with proper
6
+ error handling, validation, and reusability.
7
+
8
+ Example:
9
+ >>> relay = USBRelayDevice() # Default device
10
+ >>> relay.turn_on(1)
11
+ >>> relay.get_state()
12
+ {'R1': 'ON', 'R2': 'OFF'}
13
+
14
+ >>> specific = USBRelayDevice(device_id='HURTM')
15
+ >>> specific.turn_on_all()
16
+
17
+ >>> # Context manager pattern
18
+ >>> with USBRelayDevice('HURTM') as relay:
19
+ ... relay.turn_on_all()
4
20
  """
5
21
 
22
+ import logging
6
23
  import os
7
24
  import platform
25
+ import re
8
26
  import subprocess
9
- from typing import Optional, Tuple, List
27
+ from dataclasses import dataclass
28
+ from enum import Enum
29
+ from functools import lru_cache
30
+ from pathlib import Path
31
+ from typing import Dict, List, Optional, Union
10
32
 
11
- def get_platform_and_architecture() -> Tuple[str, str]:
12
- """
13
- Get the current system platform and architecture.
33
+ __all__ = [
34
+ 'USBRelayDevice',
35
+ 'enumerate_devices',
36
+ 'RelayState',
37
+ 'RelayError',
38
+ 'RelayCommandError',
39
+ 'RelayValidationError',
40
+ ]
14
41
 
15
- Returns:
16
- tuple: (system, architecture), both lowercase strings.
17
- """
18
- return platform.system().lower(), platform.architecture()[0].lower()
42
+ # Configure module logger
43
+ logger = logging.getLogger(__name__)
19
44
 
20
- def get_bin_path() -> str:
21
- """
22
- Get the absolute path to the package's binary folder.
45
+ # Constants
46
+ DEFAULT_TIMEOUT = 5.0
47
+ MAX_RELAY_COUNT = 8
23
48
 
24
- Returns:
25
- str: Path to the binary folder.
26
- """
27
- return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'hid_usb_relay_bin')
28
49
 
29
- def get_relay_path(file_name: str) -> str:
30
- """
31
- Build the path to a relay binary/library for the current platform and architecture.
50
+ class RelayState(Enum):
51
+ """Relay state enumeration."""
52
+ ON = "on"
53
+ OFF = "off"
32
54
 
33
- Args:
34
- file_name (str): Name of the binary/library file.
35
55
 
36
- Returns:
37
- str: Full path to the file.
56
+ class RelayCommand(Enum):
57
+ """Available relay commands."""
58
+ STATE = "state"
59
+ ENUM = "enum"
38
60
 
39
- Raises:
40
- OSError: If the system platform is unsupported.
41
- """
42
- bin_dir = get_bin_path()
43
- system, arch = get_platform_and_architecture()
44
- if system not in ['windows', 'linux']:
45
- raise OSError(f'Unsupported system platform: {system}')
46
- return os.path.join(bin_dir, system, arch, file_name)
47
61
 
48
- def get_relay_executable() -> str:
49
- """
50
- Get the path to the relay command-line executable for the current platform.
62
+ class RelayError(Exception):
63
+ """Base exception for relay operations."""
64
+ pass
51
65
 
52
- Returns:
53
- str: Full path to the relay executable.
54
- """
55
- exe_name = "hidusb-relay-cmd.exe" if platform.system().lower() == 'windows' else "hidusb-relay-cmd"
56
- return get_relay_path(exe_name)
57
66
 
58
- def get_relay_library() -> str:
67
+ class RelayCommandError(RelayError):
68
+ """Raised when relay command execution fails."""
69
+ pass
70
+
71
+
72
+ class RelayValidationError(RelayError):
73
+ """Raised when input validation fails."""
74
+ pass
75
+
76
+
77
+ @dataclass(frozen=True)
78
+ class PlatformInfo:
79
+ """Platform and architecture information."""
80
+ system: str
81
+ architecture: str
82
+
83
+ @property
84
+ def is_windows(self) -> bool:
85
+ return self.system == 'windows'
86
+
87
+ @property
88
+ def is_linux(self) -> bool:
89
+ return self.system == 'linux'
90
+
91
+ @property
92
+ def arch_bits(self) -> str:
93
+ """Get architecture as bit string (32bit/64bit)."""
94
+ return '64bit' if '64' in self.architecture else '32bit'
95
+
96
+
97
+ @lru_cache(maxsize=1)
98
+ def get_platform_info() -> PlatformInfo:
59
99
  """
60
- Get the path to the relay library for the current platform.
100
+ Get cached platform information.
61
101
 
62
102
  Returns:
63
- str: Full path to the relay library.
103
+ PlatformInfo: Platform and architecture details.
64
104
  """
65
- lib_name = "USB_RELAY_DEVICE.dll" if platform.system().lower() == 'windows' else "usb_relay_device.so"
66
- return get_relay_path(lib_name)
105
+ return PlatformInfo(
106
+ system=platform.system().lower(),
107
+ architecture=platform.architecture()[0].lower()
108
+ )
109
+
67
110
 
68
- def run_command(command: List[str]) -> Optional[str]:
111
+ def _get_module_bin_directory() -> Path:
112
+ """Get the default binaries folder in module directory."""
113
+ return Path(__file__).parent / 'hid_usb_relay_bin'
114
+
115
+
116
+ def get_bin_directory(base_path: Optional[Union[str, Path]] = None) -> Path:
69
117
  """
70
- Run a command using subprocess and return its output.
118
+ Get the absolute path to the binaries folder.
71
119
 
72
120
  Args:
73
- command (List[str]): Command and arguments as a list.
121
+ base_path: Optional custom base path. If None, uses module directory.
74
122
 
75
123
  Returns:
76
- Optional[str]: Output string if successful, None otherwise.
124
+ Path: Path to the binary folder.
77
125
  """
78
- process = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
79
- if process.returncode == 0:
80
- return process.stdout.strip()
81
- print(f"Error executing command: {process.stderr}")
82
- return None
126
+ if base_path is None:
127
+ return _get_module_bin_directory()
128
+ return Path(base_path) / 'hid_usb_relay_bin'
83
129
 
84
- def get_default_relay_device_state() -> Optional[List[str]]:
85
- """
86
- Get the status of the default relay device.
87
130
 
88
- Returns:
89
- Optional[List[str]]: List of relay states, or None on error.
90
- """
91
- output = run_command([get_relay_executable(), "STATUS"])
92
- return output.split(':')[-1].strip().split(' ') if output else None
131
+ @lru_cache(maxsize=1)
132
+ def _get_default_executable_path() -> Path:
133
+ """Get cached default executable path."""
134
+ plat = get_platform_info()
93
135
 
94
- def set_default_relay_device_state(relay_state: str) -> bool:
136
+ if not (plat.is_windows or plat.is_linux):
137
+ raise RelayError(f'Unsupported platform: {plat.system}')
138
+
139
+ bin_dir = _get_module_bin_directory()
140
+ exe_name = "hidusb-relay-cmd.exe" if plat.is_windows else "hidusb-relay-cmd"
141
+ exe_path = bin_dir / plat.system / plat.arch_bits / exe_name
142
+
143
+ if not exe_path.exists():
144
+ raise RelayError(f'Executable not found: {exe_path}')
145
+
146
+ return exe_path
147
+
148
+
149
+ def get_executable_path(base_path: Optional[Union[str, Path]] = None) -> Path:
95
150
  """
96
- Set all relays on the default device to a given state.
151
+ Get the path to the relay command-line executable.
97
152
 
98
153
  Args:
99
- relay_state (str): "ON" or "OFF".
154
+ base_path: Optional custom base path for binaries. If None, uses cached default.
100
155
 
101
156
  Returns:
102
- bool: True if successful, False otherwise.
157
+ Path: Full path to the relay executable.
158
+
159
+ Raises:
160
+ RelayError: If platform is unsupported or executable not found.
103
161
  """
104
- return run_command([get_relay_executable(), relay_state, "ALL"]) is not None
162
+ if base_path is None:
163
+ return _get_default_executable_path()
105
164
 
106
- def get_relay_device_state(relay_id: str) -> Optional[List[str]]:
165
+ plat = get_platform_info()
166
+
167
+ if not (plat.is_windows or plat.is_linux):
168
+ raise RelayError(f'Unsupported platform: {plat.system}')
169
+
170
+ bin_dir = get_bin_directory(base_path)
171
+ exe_name = "hidusb-relay-cmd.exe" if plat.is_windows else "hidusb-relay-cmd"
172
+ exe_path = bin_dir / plat.system / plat.arch_bits / exe_name
173
+
174
+ if not exe_path.exists():
175
+ raise RelayError(f'Executable not found: {exe_path}')
176
+
177
+ return exe_path
178
+
179
+
180
+ def _execute_command(command: List[str], timeout: float = DEFAULT_TIMEOUT) -> str:
107
181
  """
108
- Get the status of a specific relay device by ID.
182
+ Execute a relay command and return output.
109
183
 
110
184
  Args:
111
- relay_id (str): Relay device ID.
185
+ command: Command and arguments as list.
186
+ timeout: Command timeout in seconds.
112
187
 
113
188
  Returns:
114
- Optional[List[str]]: List of relay states, or None on error.
115
- """
116
- output = run_command([get_relay_executable(), f"id={relay_id}", "STATUS"])
117
- return output.split(':')[-1].strip().split(' ') if output else None
189
+ str: Command output (empty string if no output).
118
190
 
119
- def set_relay_device_state(relay_id: str, relay_state: str) -> bool:
191
+ Raises:
192
+ RelayCommandError: If command execution fails.
193
+ """
194
+ # Convert Path objects to strings for subprocess
195
+ cmd_str = [str(c) for c in command]
196
+ logger.debug(f"Executing: {' '.join(cmd_str)}")
197
+
198
+ try:
199
+ result = subprocess.run(
200
+ cmd_str,
201
+ stdout=subprocess.PIPE,
202
+ stderr=subprocess.PIPE,
203
+ text=True,
204
+ check=True,
205
+ timeout=timeout
206
+ )
207
+ output = result.stdout.strip() if result.stdout else ""
208
+ if output:
209
+ logger.debug(f"Output: {output}")
210
+ return output
211
+
212
+ except subprocess.CalledProcessError as e:
213
+ error_msg = f"Command failed (exit {e.returncode}): {e.stderr.strip() if e.stderr else 'Unknown error'}"
214
+ logger.error(error_msg)
215
+ raise RelayCommandError(error_msg) from e
216
+ except subprocess.TimeoutExpired as e:
217
+ error_msg = f"Command timed out after {timeout}s"
218
+ logger.error(error_msg)
219
+ raise RelayCommandError(error_msg) from e
220
+ except FileNotFoundError as e:
221
+ error_msg = f"Executable not found: {command[0]}"
222
+ logger.error(error_msg)
223
+ raise RelayCommandError(error_msg) from e
224
+ except OSError as e:
225
+ error_msg = f"OS error executing command: {e}"
226
+ logger.error(error_msg)
227
+ raise RelayCommandError(error_msg) from e
228
+
229
+
230
+ def _parse_relay_states(output: str) -> Dict[str, str]:
120
231
  """
121
- Set all relays on a specific device to a given state.
232
+ Parse relay state output into a dictionary.
122
233
 
123
234
  Args:
124
- relay_id (str): Relay device ID.
125
- relay_state (str): "ON" or "OFF".
235
+ output: Raw command output (e.g., "Board ID=[BITFT] State: R1=OFF R2=OFF").
126
236
 
127
237
  Returns:
128
- bool: True if successful, False otherwise.
129
- """
130
- return run_command([get_relay_executable(), f"id={relay_id}", relay_state, "ALL"]) is not None
238
+ Dict mapping relay names to states (e.g., {'R1': 'OFF', 'R2': 'OFF'}).
131
239
 
132
- def get_all_relay_device_state() -> Optional[str]:
240
+ Raises:
241
+ RelayError: If output format is invalid.
133
242
  """
134
- Get the status of all connected relay devices.
243
+ if not output or 'State:' not in output:
244
+ raise RelayError(f"Invalid state format: {output}")
135
245
 
136
- Returns:
137
- Optional[str]: Status string, or None on error.
138
- """
139
- return run_command([get_relay_executable(), "ENUM"])
246
+ # Extract state portion after "State:"
247
+ state_str = output.split('State:', 1)[-1].strip()
140
248
 
141
- def get_relay_device_relay_state(relay_id: str, relay_number: str) -> Optional[str]:
142
- """
143
- Get the state of a specific relay on a specific device.
249
+ # Parse relay states using regex for robustness
250
+ # Matches patterns like R1=OFF, R2=ON
251
+ pattern = re.compile(r'(R\d+)=(ON|OFF)', re.IGNORECASE)
252
+ matches = pattern.findall(state_str)
144
253
 
145
- Args:
146
- relay_id (str): Relay device ID.
147
- relay_number (str): Relay number as string (e.g., "1").
254
+ if not matches:
255
+ raise RelayError(f"No relay states found in: {output}")
148
256
 
149
- Returns:
150
- Optional[str]: State ("ON"/"OFF") or None on error.
151
- """
152
- states = get_relay_device_state(relay_id)
153
- if states:
154
- return states[int(relay_number) - 1].split('=')[-1]
155
- return None
257
+ return {relay: state.upper() for relay, state in matches}
156
258
 
157
- def set_relay_device_relay_state(relay_id: str, relay_number: str, relay_state: str) -> bool:
259
+
260
+ def _validate_relay_number(relay_num: Union[int, str], max_relays: int = MAX_RELAY_COUNT) -> int:
158
261
  """
159
- Set the state of a specific relay on a specific device.
262
+ Validate and convert relay number.
160
263
 
161
264
  Args:
162
- relay_id (str): Relay device ID.
163
- relay_number (str): Relay number as string (e.g., "1").
164
- relay_state (str): "ON" or "OFF".
265
+ relay_num: Relay number as int or string.
266
+ max_relays: Maximum valid relay number.
165
267
 
166
268
  Returns:
167
- bool: True if successful, False otherwise.
168
- """
169
- return run_command([get_relay_executable(), f"id={relay_id}", relay_state, relay_number]) is not None
269
+ int: Validated relay number.
170
270
 
171
- def get_default_relay_device_relay_state(relay_number: str) -> Optional[str]:
271
+ Raises:
272
+ RelayValidationError: If relay number is invalid.
172
273
  """
173
- Get the state of a specific relay on the default device.
274
+ try:
275
+ num = int(relay_num)
276
+ except (ValueError, TypeError) as e:
277
+ raise RelayValidationError(
278
+ f"Invalid relay number format: {relay_num!r}"
279
+ ) from e
174
280
 
175
- Args:
176
- relay_number (str): Relay number as string (e.g., "1").
281
+ if not 1 <= num <= max_relays:
282
+ raise RelayValidationError(
283
+ f"Relay number must be between 1 and {max_relays}, got {num}"
284
+ )
177
285
 
178
- Returns:
179
- Optional[str]: State ("ON"/"OFF") or None on error.
286
+ return num
287
+
288
+
289
+ class USBRelayDevice:
180
290
  """
181
- states = get_default_relay_device_state()
182
- if states:
183
- return states[int(relay_number) - 1].split('=')[-1]
184
- return None
291
+ Interface for controlling a USB relay device.
185
292
 
186
- def set_default_relay_device_relay_state(relay_number: str, relay_state: str) -> bool:
293
+ Supports context manager protocol for resource management patterns.
294
+
295
+ Attributes:
296
+ device_id: Optional device ID. If None, uses default device.
297
+
298
+ Example:
299
+ >>> relay = USBRelayDevice(device_id='HURTM')
300
+ >>> relay.turn_on(1)
301
+ >>> relay.get_state()
302
+ {'R1': 'ON', 'R2': 'OFF'}
303
+
304
+ >>> with USBRelayDevice('HURTM') as relay:
305
+ ... relay.turn_on_all()
187
306
  """
188
- Set the state of a specific relay on the default device.
189
307
 
190
- Args:
191
- relay_number (str): Relay number as string (e.g., "1").
192
- relay_state (str): "ON" or "OFF".
308
+ def __init__(
309
+ self,
310
+ device_id: Optional[str] = None,
311
+ executable_path: Optional[Union[str, Path]] = None,
312
+ timeout: float = DEFAULT_TIMEOUT
313
+ ):
314
+ """
315
+ Initialize relay device controller.
316
+
317
+ Args:
318
+ device_id: Device ID. If None, controls the default device.
319
+ executable_path: Custom path to relay executable. If None, auto-detects.
320
+ timeout: Command timeout in seconds.
321
+ """
322
+ self.device_id = device_id
323
+ self._timeout = timeout
324
+ self._exe_path = Path(executable_path) if executable_path else get_executable_path()
325
+ logger.info(f"Initialized USB relay: {device_id or 'default'}")
326
+
327
+ def __enter__(self) -> 'USBRelayDevice':
328
+ """Context manager entry."""
329
+ return self
330
+
331
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
332
+ """Context manager exit."""
333
+ # Could add cleanup logic here if needed
334
+ pass
335
+
336
+ def __repr__(self) -> str:
337
+ return f"USBRelayDevice(device_id={self.device_id!r})"
338
+
339
+ def _build_command(
340
+ self,
341
+ action: str,
342
+ target: Optional[str] = None
343
+ ) -> List[Union[Path, str]]:
344
+ """
345
+ Build command list for execution.
346
+
347
+ Args:
348
+ action: Action to perform (on/off/state/enum).
349
+ target: Target relay number or "all".
350
+
351
+ Returns:
352
+ List of command arguments.
353
+ """
354
+ cmd: List[Union[Path, str]] = [self._exe_path]
355
+
356
+ if self.device_id:
357
+ cmd.append(f"id={self.device_id}")
358
+
359
+ cmd.append(action)
360
+
361
+ if target is not None:
362
+ cmd.append(target)
363
+
364
+ return cmd
365
+
366
+ def _execute(self, action: str, target: Optional[str] = None) -> str:
367
+ """Execute command with device-specific timeout."""
368
+ cmd = self._build_command(action, target)
369
+ return _execute_command(cmd, timeout=self._timeout)
370
+
371
+ def get_state(self) -> Dict[str, str]:
372
+ """
373
+ Get current state of all relays on this device.
374
+
375
+ Returns:
376
+ Dict mapping relay names to states (e.g., {'R1': 'ON', 'R2': 'OFF'}).
377
+
378
+ Raises:
379
+ RelayCommandError: If command fails.
380
+ """
381
+ output = self._execute(RelayCommand.STATE.value)
382
+ return _parse_relay_states(output)
383
+
384
+ def get_relay_state(self, relay_num: Union[int, str]) -> str:
385
+ """
386
+ Get state of a specific relay.
387
+
388
+ Args:
389
+ relay_num: Relay number (1-based).
390
+
391
+ Returns:
392
+ str: "ON" or "OFF".
393
+
394
+ Raises:
395
+ RelayValidationError: If relay number is invalid.
396
+ RelayCommandError: If command fails.
397
+ RelayError: If relay not found.
398
+ """
399
+ num = _validate_relay_number(relay_num)
400
+ states = self.get_state()
401
+ relay_key = f"R{num}"
402
+
403
+ if relay_key not in states:
404
+ raise RelayError(
405
+ f"Relay {num} not found. Available: {', '.join(states.keys())}"
406
+ )
407
+
408
+ return states[relay_key]
409
+
410
+ def set_state(
411
+ self,
412
+ state: Union[RelayState, str],
413
+ relay_num: Optional[Union[int, str]] = None
414
+ ) -> None:
415
+ """
416
+ Set relay state.
417
+
418
+ Args:
419
+ state: RelayState.ON/OFF or "on"/"off"/"ON"/"OFF" string.
420
+ relay_num: Optional relay number. If None, sets all relays.
421
+
422
+ Raises:
423
+ RelayValidationError: If inputs are invalid.
424
+ RelayCommandError: If command fails.
425
+ """
426
+ # Normalize state
427
+ if isinstance(state, RelayState):
428
+ state_str = state.value
429
+ elif isinstance(state, str):
430
+ state_str = state.lower()
431
+ if state_str not in ('on', 'off'):
432
+ raise RelayValidationError(
433
+ f"Invalid state: {state!r}. Must be 'on' or 'off'"
434
+ )
435
+ else:
436
+ raise RelayValidationError(
437
+ f"State must be RelayState or str, got {type(state).__name__}"
438
+ )
439
+
440
+ # Determine target
441
+ if relay_num is None:
442
+ target = "all"
443
+ else:
444
+ num = _validate_relay_number(relay_num)
445
+ target = str(num)
446
+
447
+ self._execute(state_str, target)
448
+ logger.info(f"Set {self.device_id or 'default'} relay {target} to {state_str.upper()}")
449
+
450
+ def turn_on(self, relay_num: Union[int, str]) -> None:
451
+ """Turn on a specific relay."""
452
+ self.set_state(RelayState.ON, relay_num)
453
+
454
+ def turn_off(self, relay_num: Union[int, str]) -> None:
455
+ """Turn off a specific relay."""
456
+ self.set_state(RelayState.OFF, relay_num)
457
+
458
+ def turn_on_all(self) -> None:
459
+ """Turn on all relays."""
460
+ self.set_state(RelayState.ON)
461
+
462
+ def turn_off_all(self) -> None:
463
+ """Turn off all relays."""
464
+ self.set_state(RelayState.OFF)
465
+
466
+
467
+ def enumerate_devices() -> List[Dict[str, str]]:
468
+ """
469
+ Enumerate all connected relay devices.
193
470
 
194
471
  Returns:
195
- bool: True if successful, False otherwise.
472
+ List of dicts containing device info and states.
473
+ Example: [
474
+ {'device_id': 'BITFT', 'R1': 'OFF', 'R2': 'OFF'},
475
+ {'device_id': 'HURTM', 'R1': 'ON', 'R2': 'ON'}
476
+ ]
477
+
478
+ Raises:
479
+ RelayCommandError: If enumeration fails.
196
480
  """
197
- return run_command([get_relay_executable(), relay_state, relay_number]) is not None
481
+ exe_path = _get_default_executable_path()
482
+ output = _execute_command([exe_path, RelayCommand.ENUM.value])
483
+
484
+ # Regex to extract device ID more robustly
485
+ device_pattern = re.compile(r'Board ID=\[([^\]]+)\]', re.IGNORECASE)
486
+
487
+ devices = []
488
+ for line in output.split('\n'):
489
+ if not line.strip():
490
+ continue
491
+
492
+ match = device_pattern.search(line)
493
+ if match:
494
+ device_id = match.group(1)
495
+ try:
496
+ states = _parse_relay_states(line)
497
+ devices.append({'device_id': device_id, **states})
498
+ except RelayError as e:
499
+ logger.warning(f"Failed to parse device {device_id}: {e}")
500
+
501
+ logger.info(f"Found {len(devices)} relay device(s)")
502
+ return devices
503
+
504
+
505
+ # Backward compatibility functions (delegating to class-based API)
506
+ def get_default_relay_device_state() -> Optional[List[str]]:
507
+ """DEPRECATED: Use USBRelayDevice().get_state() instead."""
508
+ try:
509
+ states = USBRelayDevice().get_state()
510
+ return [f"{k}={v}" for k, v in states.items()]
511
+ except RelayError:
512
+ return None
513
+
514
+
515
+ def set_default_relay_device_state(relay_state: str) -> bool:
516
+ """DEPRECATED: Use USBRelayDevice().set_state() instead."""
517
+ try:
518
+ USBRelayDevice().set_state(relay_state)
519
+ return True
520
+ except RelayError:
521
+ return False
522
+
523
+
524
+ def get_relay_device_state(relay_id: str) -> Optional[List[str]]:
525
+ """DEPRECATED: Use USBRelayDevice(device_id).get_state() instead."""
526
+ try:
527
+ states = USBRelayDevice(device_id=relay_id).get_state()
528
+ return [f"{k}={v}" for k, v in states.items()]
529
+ except RelayError:
530
+ return None
531
+
532
+
533
+ def set_relay_device_state(relay_id: str, relay_state: str) -> bool:
534
+ """DEPRECATED: Use USBRelayDevice(device_id).set_state() instead."""
535
+ try:
536
+ USBRelayDevice(device_id=relay_id).set_state(relay_state)
537
+ return True
538
+ except RelayError:
539
+ return False
540
+
541
+
542
+ def get_all_relay_device_state() -> Optional[str]:
543
+ """DEPRECATED: Use enumerate_devices() instead."""
544
+ try:
545
+ exe_path = get_executable_path()
546
+ return _execute_command([exe_path, RelayCommand.ENUM.value])
547
+ except RelayError:
548
+ return None
549
+
550
+
551
+ def get_relay_device_relay_state(relay_id: str, relay_number: str) -> Optional[str]:
552
+ """DEPRECATED: Use USBRelayDevice(device_id).get_relay_state() instead."""
553
+ try:
554
+ return USBRelayDevice(device_id=relay_id).get_relay_state(relay_number)
555
+ except RelayError:
556
+ return None
557
+
558
+
559
+ def set_relay_device_relay_state(relay_id: str, relay_number: str, relay_state: str) -> bool:
560
+ """DEPRECATED: Use USBRelayDevice(device_id).set_state() instead."""
561
+ try:
562
+ USBRelayDevice(device_id=relay_id).set_state(relay_state, relay_number)
563
+ return True
564
+ except RelayError:
565
+ return False
566
+
567
+
568
+ def get_default_relay_device_relay_state(relay_number: str) -> Optional[str]:
569
+ """DEPRECATED: Use USBRelayDevice().get_relay_state() instead."""
570
+ try:
571
+ return USBRelayDevice().get_relay_state(relay_number)
572
+ except RelayError:
573
+ return None
574
+
575
+
576
+ def set_default_relay_device_relay_state(relay_number: str, relay_state: str) -> bool:
577
+ """DEPRECATED: Use USBRelayDevice().set_state() instead."""
578
+ try:
579
+ USBRelayDevice().set_state(relay_state, relay_number)
580
+ return True
581
+ except RelayError:
582
+ return False