mesk 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.
Files changed (88) hide show
  1. mesk/__init__.py +10 -0
  2. mesk/__main__.py +80 -0
  3. mesk/__version__.py +1 -0
  4. mesk/py.typed +0 -0
  5. mesk/runner.py +224 -0
  6. mesk/vendor/__init__.py +0 -0
  7. mesk/vendor/boardctrl_phidget/README.md +244 -0
  8. mesk/vendor/boardctrl_phidget/__init__.py +0 -0
  9. mesk/vendor/boardctrl_phidget/monitor.py +232 -0
  10. mesk/vendor/boardctrl_phidget/phidget.py +224 -0
  11. mesk/vendor/boardctrl_phidget/phidget_control.sh +324 -0
  12. mesk/vendor/topo/Android.mk +27 -0
  13. mesk/vendor/topo/TCA9539.py +70 -0
  14. mesk/vendor/topo/__init__.py +0 -0
  15. mesk/vendor/topo/_targets/_PCA953x.py +134 -0
  16. mesk/vendor/topo/_targets/_TCA6408.py +122 -0
  17. mesk/vendor/topo/_targets/__init__.py +0 -0
  18. mesk/vendor/topo/_targets/_common.py +203 -0
  19. mesk/vendor/topo/_targets/_nv_base_orin_target.py +37 -0
  20. mesk/vendor/topo/_targets/_nv_base_thor_target.py +47 -0
  21. mesk/vendor/topo/_targets/_nv_base_topo_target.py +249 -0
  22. mesk/vendor/topo/_targets/_nv_thor_target.py +21 -0
  23. mesk/vendor/topo/_targets/_nv_topo_target.py +21 -0
  24. mesk/vendor/topo/_targets/concord.py +21 -0
  25. mesk/vendor/topo/_targets/horizon-eb1.py +99 -0
  26. mesk/vendor/topo/_targets/jane.py +44 -0
  27. mesk/vendor/topo/_targets/orin-jetson.py +21 -0
  28. mesk/vendor/topo/_targets/orin-slt.py +32 -0
  29. mesk/vendor/topo/_targets/t239-slt.py +28 -0
  30. mesk/vendor/topo/_targets/t268-slt.py +262 -0
  31. mesk/vendor/topo/_targets/tango.py +75 -0
  32. mesk/vendor/topo/_targets/thor-andor-slt.py +253 -0
  33. mesk/vendor/topo/_targets/thor-cassian-slt.py +21 -0
  34. mesk/vendor/topo/_targets/thor-drive-agx.py +21 -0
  35. mesk/vendor/topo/_targets/thor-drive-agx2.py +105 -0
  36. mesk/vendor/topo/_targets/thor-drive-pm.py +21 -0
  37. mesk/vendor/topo/_targets/thor-hope.py +21 -0
  38. mesk/vendor/topo/_targets/thor-jetson-devkit.py +21 -0
  39. mesk/vendor/topo/_targets/thor-jetson-module.py +21 -0
  40. mesk/vendor/topo/_targets/thor-l4t-pm.py +21 -0
  41. mesk/vendor/topo/_targets/thor-slt.py +262 -0
  42. mesk/vendor/topo/_targets/topo.py +21 -0
  43. mesk/vendor/topo/_targets/yukon.py +75 -0
  44. mesk/vendor/topo/bmc_interface.py +441 -0
  45. mesk/vendor/topo/board_control.py +74 -0
  46. mesk/vendor/topo/board_info.py +104 -0
  47. mesk/vendor/topo/boardctl +216 -0
  48. mesk/vendor/topo/debug_board.py +308 -0
  49. mesk/vendor/topo/e2379.py +130 -0
  50. mesk/vendor/topo/ftdi_i2c_lin.py +337 -0
  51. mesk/vendor/topo/ftdi_wrapper.py +103 -0
  52. mesk/vendor/topo/galen_power.py +40 -0
  53. mesk/vendor/topo/ina219.py +167 -0
  54. mesk/vendor/topo/ina226.py +197 -0
  55. mesk/vendor/topo/ina3221.py +135 -0
  56. mesk/vendor/topo/libnvtopo.so +0 -0
  57. mesk/vendor/topo/libnvtopo_wrapper.py +516 -0
  58. mesk/vendor/topo/lspm342.py +49 -0
  59. mesk/vendor/topo/measure +324 -0
  60. mesk/vendor/topo/measure_power +130 -0
  61. mesk/vendor/topo/metadata.py +158 -0
  62. mesk/vendor/topo/nvtopo.py +516 -0
  63. mesk/vendor/topo/pca954x.py +41 -0
  64. mesk/vendor/topo/pm342.py +122 -0
  65. mesk/vendor/topo/power.py +93 -0
  66. mesk/vendor/topo/requirements-bmc.txt +5 -0
  67. mesk/vendor/topo/supported_targets.py +16 -0
  68. mesk/vendor/topo/supported_targets_all.py +16 -0
  69. mesk/vendor/topo/tb500-ctl +792 -0
  70. mesk/vendor/topo/tckctrl +110 -0
  71. mesk/vendor/topo/th500-ctl +282 -0
  72. mesk/vendor/topo/utils.py +91 -0
  73. mesk/vendor/topo_yukon/README.txt +41 -0
  74. mesk/vendor/topo_yukon/__init__.py +6 -0
  75. mesk/vendor/topo_yukon/_targets/__init__.py +0 -0
  76. mesk/vendor/topo_yukon/_targets/_common.py +202 -0
  77. mesk/vendor/topo_yukon/_targets/_nv_base_topo_target.py +246 -0
  78. mesk/vendor/topo_yukon/_targets/yukon.py +75 -0
  79. mesk/vendor/topo_yukon/boardctl +216 -0
  80. mesk/vendor/topo_yukon/libnvtopo.dll +0 -0
  81. mesk/vendor/topo_yukon/libnvtopo.so +0 -0
  82. mesk/vendor/topo_yukon/libnvtopo_wrapper.py +513 -0
  83. mesk/vendor/topo_yukon/nvtopo.py +511 -0
  84. mesk-0.1.0.dist-info/METADATA +205 -0
  85. mesk-0.1.0.dist-info/RECORD +88 -0
  86. mesk-0.1.0.dist-info/WHEEL +5 -0
  87. mesk-0.1.0.dist-info/entry_points.txt +2 -0
  88. mesk-0.1.0.dist-info/top_level.txt +1 -0
mesk/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ """Unified power and reset control for Concord, N1-auto, Yukon, and Tango platforms."""
2
+
3
+ from .__version__ import __version__
4
+ from .runner import ACTIONS, run
5
+
6
+ __all__ = [
7
+ "ACTIONS",
8
+ "run",
9
+ "__version__",
10
+ ]
mesk/__main__.py ADDED
@@ -0,0 +1,80 @@
1
+ """CLI entry point for mesk."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+
8
+ from mesk.runner import _PLATFORMS, ACTIONS, run
9
+
10
+ _SUDO_PLATFORMS = frozenset({"concord"})
11
+
12
+
13
+ def _needs_sudo(platform: str) -> bool:
14
+ if platform not in _SUDO_PLATFORMS:
15
+ return False
16
+ if os.environ.get("MESK_NO_SUDO"):
17
+ return False
18
+ try:
19
+ return os.geteuid() != 0
20
+ except AttributeError:
21
+ return False
22
+
23
+
24
+ def _reexec_with_sudo() -> None:
25
+ os.execvp("sudo", ["sudo", sys.executable, "-m", "mesk", *sys.argv[1:]])
26
+
27
+
28
+ def _phidget_lib_present() -> bool:
29
+ from ctypes.util import find_library
30
+
31
+ return find_library("phidget22") is not None
32
+
33
+
34
+ def _ensure_libphidget22() -> None:
35
+ if _phidget_lib_present():
36
+ return
37
+ if os.environ.get("MESK_NO_SUDO"):
38
+ raise RuntimeError("libphidget22 is missing; install it or unset MESK_NO_SUDO")
39
+ import subprocess
40
+
41
+ print("Installing libphidget22 (may prompt for sudo)...")
42
+ apt = subprocess.run(["sudo", "apt-get", "install", "-y", "libphidget22"])
43
+ if apt.returncode == 0 and _phidget_lib_present():
44
+ return
45
+ print("Adding Phidget apt repository...")
46
+ setup = subprocess.run(
47
+ ["curl", "-fsSL", "https://www.phidgets.com/downloads/setup_linux"],
48
+ check=True,
49
+ stdout=subprocess.PIPE,
50
+ )
51
+ subprocess.run(["sudo", "bash"], input=setup.stdout, check=True)
52
+ subprocess.check_call(["sudo", "apt-get", "install", "-y", "libphidget22"])
53
+ if not _phidget_lib_present():
54
+ raise RuntimeError("libphidget22 is still missing after install")
55
+
56
+
57
+ def _ensure_phidget22() -> None:
58
+ import importlib.util
59
+ import subprocess
60
+
61
+ if importlib.util.find_spec("Phidget22") is None:
62
+ print("Installing Phidget22 into this environment...")
63
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "Phidget22"])
64
+ _ensure_libphidget22()
65
+
66
+
67
+ def main() -> None:
68
+ platforms = list(_PLATFORMS)
69
+ if len(sys.argv) != 3 or sys.argv[1] not in platforms or sys.argv[2] not in ACTIONS:
70
+ print(f"Usage: mesk <{'|'.join(platforms)}> <{'|'.join(ACTIONS)}>")
71
+ sys.exit(1)
72
+ if sys.argv[1] == "n1-auto":
73
+ _ensure_phidget22()
74
+ if _needs_sudo(sys.argv[1]):
75
+ _reexec_with_sudo()
76
+ sys.exit(run(sys.argv[1], sys.argv[2]))
77
+
78
+
79
+ if __name__ == "__main__":
80
+ main()
mesk/__version__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
mesk/py.typed ADDED
File without changes
mesk/runner.py ADDED
@@ -0,0 +1,224 @@
1
+ """Execute platform power/reset actions via in-process vendor libraries."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ import io
7
+ import os
8
+ import sys
9
+ import time
10
+ from datetime import datetime
11
+ from importlib import import_module
12
+ from pathlib import Path
13
+ from types import ModuleType
14
+ from typing import Any, Callable
15
+
16
+ _VENDOR_ROOT = Path(__file__).resolve().parent / "vendor"
17
+ _log_dir_env = os.environ.get("MESK_LOG_DIR")
18
+ _LOG_DIR = Path(_log_dir_env) if _log_dir_env else Path.cwd() / "logs"
19
+
20
+ # platform -> (vendor subdirectory, nv_topo target name or None for phidget)
21
+ _PLATFORMS: dict[str, tuple[str, str | None]] = {
22
+ "concord": ("topo", "topo"),
23
+ "n1-auto": ("boardctrl_phidget", None),
24
+ "yukon": ("topo_yukon", "yukon"),
25
+ "tango": ("topo_yukon", "yukon"),
26
+ }
27
+
28
+ ACTIONS = ("reset", "recovery", "power_on", "power_off")
29
+
30
+ _N1_RESET_RELAY = 1
31
+ _N1_RECOVERY_RELAY = 0
32
+ _N1_POWER_RELAY = 2
33
+
34
+ _NVIDIA_VID = 0x0955
35
+ _CONCORD_APX_PID = 0x7023
36
+
37
+ _TOPO_MODULE_PREFIXES = (
38
+ "nvtopo",
39
+ "libnvtopo_wrapper",
40
+ "board_info",
41
+ "_targets",
42
+ )
43
+
44
+
45
+ def _module_file(mod: object) -> str | None:
46
+ path = getattr(mod, "__file__", None)
47
+ return str(Path(path).resolve()) if path else None
48
+
49
+
50
+ def _evict_modules_from(vendor_dir: Path) -> None:
51
+ prefix = str(vendor_dir.resolve())
52
+ doomed = []
53
+ for name, mod in sys.modules.items():
54
+ file = _module_file(mod)
55
+ if file is not None and file.startswith(prefix + os.sep):
56
+ doomed.append(name)
57
+ elif name in _TOPO_MODULE_PREFIXES or name.startswith("_targets."):
58
+ doomed.append(name)
59
+ for name in doomed:
60
+ sys.modules.pop(name, None)
61
+
62
+
63
+ def _activate_vendor_path(vendor_dir: Path) -> None:
64
+ path = str(vendor_dir)
65
+ sys.path = [p for p in sys.path if p != path]
66
+ sys.path.insert(0, path)
67
+
68
+
69
+ def _load_vendor_module(vendor_subdir: str, module_name: str) -> ModuleType:
70
+ vendor_dir = _VENDOR_ROOT / vendor_subdir
71
+ _evict_modules_from(vendor_dir)
72
+ _activate_vendor_path(vendor_dir)
73
+ return import_module(module_name)
74
+
75
+
76
+ def _load_nv_topo(vendor_subdir: str) -> Callable[..., Any]:
77
+ nvtopo = _load_vendor_module(vendor_subdir, "nvtopo")
78
+ return nvtopo.nv_topo
79
+
80
+
81
+ def _load_monitor() -> ModuleType:
82
+ return _load_vendor_module("boardctrl_phidget", "monitor")
83
+
84
+
85
+ def _run_topo(vendor_subdir: str, target: str, action: str) -> None:
86
+ nv_topo = _load_nv_topo(vendor_subdir)
87
+ board = nv_topo(target=target)
88
+ if action == "reset":
89
+ board.target_reset(None)
90
+ elif action == "recovery":
91
+ board.target_recovery_mode(None)
92
+ elif action == "power_on":
93
+ board.target_power_on(None)
94
+ else:
95
+ board.target_power_off()
96
+
97
+
98
+ def _run_n1_auto(action: str) -> None:
99
+ monitor = _load_monitor()
100
+ lock = Path("/tmp/phidget.lock")
101
+
102
+ if action in ("reset", "recovery"):
103
+ print("Cleaning up any existing Phidget processes...")
104
+ print("Removing any stale lock files...")
105
+
106
+ try:
107
+ lock.unlink()
108
+ except OSError:
109
+ pass
110
+
111
+ if action == "reset":
112
+ if not monitor.ResetControl(_N1_RESET_RELAY).execute_reset():
113
+ raise RuntimeError("n1-auto reset failed")
114
+ print("Reset sequence completed successfully")
115
+ print("Soft RESET performed")
116
+ elif action == "recovery":
117
+ if not monitor.RecoveryControl(
118
+ _N1_RESET_RELAY, _N1_RECOVERY_RELAY
119
+ ).execute_recovery():
120
+ raise RuntimeError("n1-auto recovery failed")
121
+ print("Recovery sequence completed successfully")
122
+ print("Board is now in RECOVERY")
123
+ else:
124
+ desired_closed = action == "power_off"
125
+ ctl = monitor.PowerMonitor(_N1_POWER_RELAY, desired_closed)
126
+ if not ctl.initialize():
127
+ raise RuntimeError("n1-auto power control failed to initialize")
128
+ try:
129
+ print(
130
+ f"Controlling power relay ({_N1_POWER_RELAY})"
131
+ " - Normally Open configuration"
132
+ )
133
+ if desired_closed:
134
+ print("Closing circuit (turning power OFF)...")
135
+ ctl.phidget.relay_on(_N1_POWER_RELAY)
136
+ else:
137
+ print("Opening circuit (turning power ON)...")
138
+ ctl.phidget.relay_off(_N1_POWER_RELAY)
139
+ time.sleep(0.1)
140
+ if desired_closed:
141
+ print("Power circuit is CLOSED (OFF)")
142
+ print("SOC power OFF")
143
+ else:
144
+ print("Power circuit is OPEN (ON)")
145
+ print("SOC power ON")
146
+ finally:
147
+ ctl.cleanup()
148
+
149
+
150
+ def _nvidia_usb_devices() -> list[Any]:
151
+ import usb.core
152
+
153
+ found = usb.core.find(find_all=True, idVendor=_NVIDIA_VID)
154
+ return list(found) if found else []
155
+
156
+
157
+ def _verify_concord_recovery() -> None:
158
+ """Confirm APX (0955:7023) after Concord recovery; boardctl status is unreliable."""
159
+ devices: list[Any] = []
160
+ for _ in range(5):
161
+ devices = _nvidia_usb_devices()
162
+ if any(int(dev.idProduct) == _CONCORD_APX_PID for dev in devices):
163
+ break
164
+ time.sleep(1)
165
+ print("lsusb check (NVIDIA 0955:*):")
166
+ if devices:
167
+ for dev in devices:
168
+ pid = int(dev.idProduct)
169
+ label = " NVIDIA Corp. APX" if pid == _CONCORD_APX_PID else ""
170
+ print(f" 0955:{pid:04x}{label}")
171
+ else:
172
+ print(" (none)")
173
+ if not any(int(dev.idProduct) == _CONCORD_APX_PID for dev in devices):
174
+ raise RuntimeError(
175
+ "Concord recovery check failed: 0955:7023 (APX) not found in lsusb"
176
+ )
177
+ print("enter into recovery mode pass")
178
+
179
+
180
+ def _dispatch(platform: str, action: str) -> None:
181
+ vendor_subdir, target = _PLATFORMS[platform]
182
+ if target is None:
183
+ _run_n1_auto(action)
184
+ return
185
+ _run_topo(vendor_subdir, target, action)
186
+ if platform == "concord" and action == "recovery":
187
+ _verify_concord_recovery()
188
+
189
+
190
+ def _exit_code(exc: SystemExit) -> int:
191
+ if exc.code is None:
192
+ return 0
193
+ if isinstance(exc.code, int):
194
+ return exc.code
195
+ return 1
196
+
197
+
198
+ def run(platform: str, action: str) -> int:
199
+ """Execute an action on the given platform. Returns 0 on success."""
200
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
201
+ buf = io.StringIO()
202
+ code = 0
203
+ try:
204
+ with contextlib.redirect_stdout(buf), contextlib.redirect_stderr(buf):
205
+ _dispatch(platform, action)
206
+ except SystemExit as exc:
207
+ if exc.code not in (None, 0):
208
+ buf.write(f"{exc.code}\n")
209
+ code = _exit_code(exc)
210
+ except Exception as exc:
211
+ buf.write(f"{type(exc).__name__}: {exc}\n")
212
+ code = 1
213
+
214
+ output = buf.getvalue()
215
+ print(output, end="")
216
+
217
+ _LOG_DIR.mkdir(parents=True, exist_ok=True)
218
+ log_file = _LOG_DIR / (timestamp[:10] + ".log")
219
+ with open(log_file, "a", encoding="utf-8") as f:
220
+ f.write(f"[{timestamp}] platform={platform} action={action} exit_code={code}\n")
221
+ f.write(output)
222
+ f.write("\n")
223
+
224
+ return code
File without changes
@@ -0,0 +1,244 @@
1
+ # Phidget Relay Control Suite
2
+
3
+ This project provides a robust, scriptable interface for controlling Phidget relay devices, supporting operations such as reset, recovery, and power cycling. It is designed for safe, automated hardware control, with both Python and shell script interfaces.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ - [Overview](#overview)
10
+ - [File Structure](#file-structure)
11
+ - [Setup](#setup)
12
+ - [Usage](#usage)
13
+ - [phidget_control.sh](#phidget_controlsh)
14
+ - [monitor.py](#monitorpy)
15
+ - [phidget.py](#phidgetpy)
16
+ - [Detailed Code Reference](#detailed-code-reference)
17
+ - [monitor.py](#monitorpy-1)
18
+ - [phidget.py](#phidgetpy-1)
19
+ - [phidget_control.sh](#phidget_controlsh-1)
20
+ - [Extending and Maintaining](#extending-and-maintaining)
21
+ - [Troubleshooting](#troubleshooting)
22
+
23
+ ---
24
+
25
+ ## Overview
26
+
27
+ This suite is intended for environments where Phidget relay boards are used to control hardware (e.g., for automated testing, power cycling, or recovery operations). It provides:
28
+
29
+ - Safe, exclusive access to the hardware (via lock files)
30
+ - High-level operations (reset, recovery, power control)
31
+ - Low-level relay control and detection
32
+ - Easy command-line interface with environment setup
33
+
34
+ ---
35
+
36
+ ## File Structure
37
+
38
+ ```
39
+ monitor.py # High-level control and monitoring logic
40
+ phidget.py # Low-level Phidget relay interface
41
+ phidget_control.sh # User-facing shell script for all operations
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Setup
47
+
48
+ 1. **Install Python 3** (if not already installed).
49
+ 2. **Run the shell script**:
50
+ ```bash
51
+ ./phidget_control.sh [command] [options]
52
+ ```
53
+ The script will automatically create a virtual environment and install the required Python package (`Phidget22`).
54
+
55
+ ---
56
+
57
+ ## Usage
58
+
59
+ ### phidget_control.sh
60
+
61
+ This is the main entry point for users. It supports the following commands:
62
+
63
+ - `detect` — Detect connected Phidget relays.
64
+ - `relay [on|off|toggle] <relay_num>` — Control a specific relay.
65
+ - `sequence "<relay:action,...>"` — Execute a sequence of relay actions.
66
+ - `reset` — Perform a reset sequence (Relay 1).
67
+ - `recovery` — Perform a recovery sequence (Relays 0 & 1).
68
+ - `poweron` / `poweroff` — Control the power relay (Relay 2).
69
+
70
+ **Examples:**
71
+ ```bash
72
+ ./phidget_control.sh detect
73
+ ./phidget_control.sh relay on 1
74
+ ./phidget_control.sh sequence "1:on,2:off"
75
+ ./phidget_control.sh reset
76
+ ./phidget_control.sh poweroff
77
+ ```
78
+
79
+ ### monitor.py
80
+
81
+ This script provides high-level operations and should not be run directly except for advanced use. It supports:
82
+
83
+ - `reset <relay_num>`
84
+ - `recovery <reset_relay> <recovery_relay>`
85
+ - `power <relay_num> <true|false>`
86
+
87
+ **Example:**
88
+ ```bash
89
+ python3 monitor.py reset 1
90
+ python3 monitor.py recovery 1 0
91
+ python3 monitor.py power 2 true
92
+ ```
93
+
94
+ ### phidget.py
95
+
96
+ This is a library for direct relay control. It can also be run directly for testing relay sequences.
97
+
98
+ **Example:**
99
+ ```bash
100
+ python3 phidget.py -seq "1:on,2:off"
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Detailed Code Reference
106
+
107
+ ### monitor.py
108
+
109
+ #### Classes
110
+
111
+ - **BaseMonitor**
112
+ - Handles device locking (`/tmp/phidget.lock`) to prevent concurrent access.
113
+ - Manages Phidget device initialization and cleanup.
114
+ - Methods:
115
+ - `acquire_lock()`: Ensures exclusive access to the device.
116
+ - `release_lock()`: Releases the lock and cleans up.
117
+ - `cleanup()`: Closes the Phidget device and releases the lock.
118
+ - `initialize()`: Acquires lock and initializes the Phidget device.
119
+
120
+ - **ResetControl(BaseMonitor)**
121
+ - For performing a reset sequence on a specified relay.
122
+ - Methods:
123
+ - `execute_reset()`: Closes and then opens the relay, simulating a reset pulse.
124
+
125
+ - **RecoveryControl(BaseMonitor)**
126
+ - For performing a recovery sequence involving two relays.
127
+ - Methods:
128
+ - `execute_reset()`: (Internal) Performs a reset while recovery relay is held.
129
+ - `execute_recovery()`: Closes recovery relay, performs reset, then opens recovery relay.
130
+
131
+ - **PowerMonitor(BaseMonitor)**
132
+ - Maintains a relay in a desired state (on/off) in a loop.
133
+ - Methods:
134
+ - `maintain_state()`: Continuously checks and restores the relay state.
135
+
136
+ #### Main Script Logic
137
+
138
+ - Parses command-line arguments to select the operation (`reset`, `recovery`, `power`).
139
+ - Instantiates the appropriate control class and executes the requested operation.
140
+
141
+ ---
142
+
143
+ ### phidget.py
144
+
145
+ #### Classes and Functions
146
+
147
+ - **PhidgetDevice**
148
+ - Represents a Phidget device (not heavily used in the rest of the code).
149
+ - Attributes: `name`, `serial_number`, `channels`.
150
+
151
+ - **Phidget**
152
+ - Main interface to the Phidget relay hardware.
153
+ - Attributes:
154
+ - `relays`: Dictionary of relay objects (0-3).
155
+ - Methods:
156
+ - `initialize_relays()`: Initializes up to 4 relays.
157
+ - `relay_on(relay_num)`: Turns a relay ON (closes circuit).
158
+ - `relay_off(relay_num)`: Turns a relay OFF (opens circuit).
159
+ - `relay_toggle(relay_num)`: Toggles relay state.
160
+ - `execute_sequence(sequence)`: Executes a comma-separated sequence of relay actions.
161
+ - `close()`: Closes all relay connections.
162
+ - `detect_all()`: Static method to detect which relays are connected.
163
+
164
+ - **between(s, opat, cpat, last_closing)**
165
+ - Utility function to extract a substring between two delimiters (not used elsewhere).
166
+
167
+ #### Main Script Logic
168
+
169
+ - Supports command-line arguments for serial number and relay sequence.
170
+ - Can be used to test relay sequences directly.
171
+
172
+ ---
173
+
174
+ ### phidget_control.sh
175
+
176
+ #### Key Features
177
+
178
+ - **Environment Setup**
179
+ - Creates a Python virtual environment if not present.
180
+ - Installs the `Phidget22` library if missing.
181
+
182
+ - **Command Functions**
183
+ - `detect_devices()`: Uses `phidget.py` to list connected relays.
184
+ - `control_relay(action, relay)`: Turns a relay on, off, or toggles it.
185
+ - `execute_sequence(sequence)`: Runs a sequence of relay actions.
186
+ - `execute_reset()`: Runs a reset sequence using `monitor.py`.
187
+ - `execute_recovery()`: Runs a recovery sequence using `monitor.py`.
188
+ - `control_power(action)`: Maintains the power relay in the desired state using `monitor.py` in the background.
189
+
190
+ - **Process and Lock Management**
191
+ - Kills any running `monitor.py` processes before starting new operations.
192
+ - Removes stale lock files to prevent deadlocks.
193
+
194
+ - **User Guidance**
195
+ - Provides colored output and usage instructions for ease of use.
196
+
197
+ ---
198
+
199
+ ## Extending and Maintaining
200
+
201
+ - **Adding New Operations:**
202
+ Add new methods to `monitor.py` (inheriting from `BaseMonitor` if needed), and expose them via `phidget_control.sh`.
203
+
204
+ - **Relay Mapping:**
205
+ Update relay numbers in `phidget_control.sh` as needed for your hardware.
206
+
207
+ - **Error Handling:**
208
+ Both Python and shell scripts print detailed error messages. For more robust error handling, consider logging errors to a file.
209
+
210
+ - **Dependencies:**
211
+ The only Python dependency is `Phidget22`. If you add more, update the install section in the shell script.
212
+
213
+ - **Testing:**
214
+ You can test new relay sequences directly using `phidget.py` before integrating them into higher-level scripts.
215
+
216
+ ---
217
+
218
+ ## Troubleshooting
219
+
220
+ - **Device Busy/Locked:**
221
+ If you see errors about the device being in use, ensure no other process is running and remove `/tmp/phidget.lock` if necessary.
222
+
223
+ - **Phidget22 Not Installed:**
224
+ The shell script should handle this, but you can manually install with `pip install Phidget22` in the `venv`.
225
+
226
+ - **Relay Not Responding:**
227
+ Use the `detect` command to check relay connectivity.
228
+
229
+ ---
230
+
231
+ ## Contact
232
+
233
+ For further information, refer to the [Phidget Python API documentation](https://www.phidgets.com/docs/Language_-_Python).
234
+
235
+ ---
236
+
237
+ **For any issues, contact:**
238
+
239
+ **Ekjot Singh**
240
+ eksingh@nvidia.com
241
+
242
+ ---
243
+
244
+ This README should provide a comprehensive reference for using, maintaining, and extending your Phidget relay control suite. If you need a more visual diagram or have specific questions about enhancements, let me know!
File without changes