caenhv-client-python 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.
- caenhv_client_python/__init__.py +175 -0
- caenhv_client_python-0.1.0.dist-info/METADATA +41 -0
- caenhv_client_python-0.1.0.dist-info/RECORD +6 -0
- caenhv_client_python-0.1.0.dist-info/WHEEL +5 -0
- caenhv_client_python-0.1.0.dist-info/licenses/LICENSE +21 -0
- caenhv_client_python-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Python interface to the caenhv-client GUI application.
|
|
2
|
+
|
|
3
|
+
This package lets an external Python process (e.g. a labscript BLACS tab or
|
|
4
|
+
worker) *fire* the standalone caenhv-client GUI: raise the window if the app
|
|
5
|
+
is already running, or launch it otherwise. This is deliberately the only
|
|
6
|
+
remote capability — there is no remote control of HV settings.
|
|
7
|
+
|
|
8
|
+
The GUI listens on a QLocalServer (named pipe ``\\\\.\\pipe\\<name>`` on
|
|
9
|
+
Windows, Unix socket under the temp directory on POSIX). The protocol is a
|
|
10
|
+
single newline-terminated UTF-8 token: ``show`` (``raise`` is accepted as an
|
|
11
|
+
alias). Stdlib-only on every platform; PyQt5 is used as a fallback transport
|
|
12
|
+
only if it happens to be importable.
|
|
13
|
+
|
|
14
|
+
The GUI itself is distributed as a standalone executable (PyInstaller). Set
|
|
15
|
+
the ``CAENHV_CLIENT_COMMAND`` environment variable to its full path (or a
|
|
16
|
+
command line) if it is not on PATH.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
import shlex
|
|
23
|
+
import shutil
|
|
24
|
+
import socket
|
|
25
|
+
import subprocess
|
|
26
|
+
import sys
|
|
27
|
+
import tempfile
|
|
28
|
+
import time
|
|
29
|
+
|
|
30
|
+
DEFAULT_SERVER_NAME = "caenhv-client"
|
|
31
|
+
ENV_SERVER_NAME = "CAENHV_CLIENT_IPC_NAME"
|
|
32
|
+
ENV_LAUNCH_COMMAND = "CAENHV_CLIENT_COMMAND"
|
|
33
|
+
SHOW_COMMAND = b"show\n"
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"DEFAULT_SERVER_NAME",
|
|
37
|
+
"ENV_LAUNCH_COMMAND",
|
|
38
|
+
"ENV_SERVER_NAME",
|
|
39
|
+
"SHOW_COMMAND",
|
|
40
|
+
"default_launch_cmd",
|
|
41
|
+
"default_popen_kwargs",
|
|
42
|
+
"fire_gui",
|
|
43
|
+
"get_server_name",
|
|
44
|
+
"notify_gui",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
_qt_app = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_server_name(server_name: str | None = None) -> str:
|
|
51
|
+
if server_name:
|
|
52
|
+
return server_name
|
|
53
|
+
return os.environ.get(ENV_SERVER_NAME) or DEFAULT_SERVER_NAME
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _notify_via_unix_socket(name: str, timeout: float) -> bool:
|
|
57
|
+
# QLocalServer places its Unix socket in the temp dir; both Qt and
|
|
58
|
+
# tempfile honor TMPDIR, so the paths agree.
|
|
59
|
+
path = os.path.join(tempfile.gettempdir(), name)
|
|
60
|
+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
61
|
+
sock.settimeout(timeout)
|
|
62
|
+
try:
|
|
63
|
+
sock.connect(path)
|
|
64
|
+
sock.sendall(SHOW_COMMAND)
|
|
65
|
+
return True
|
|
66
|
+
except OSError:
|
|
67
|
+
return False
|
|
68
|
+
finally:
|
|
69
|
+
sock.close()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _notify_via_windows_pipe(name: str) -> bool:
|
|
73
|
+
# QLocalServer pipes are file-openable; a plain write delivers the token.
|
|
74
|
+
try:
|
|
75
|
+
with open(rf"\\.\pipe\{name}", "wb", buffering=0) as pipe:
|
|
76
|
+
pipe.write(SHOW_COMMAND)
|
|
77
|
+
return True
|
|
78
|
+
except OSError:
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _notify_via_qlocalsocket(name: str, timeout: float) -> bool:
|
|
83
|
+
from PyQt5 import QtCore, QtNetwork
|
|
84
|
+
|
|
85
|
+
global _qt_app
|
|
86
|
+
if QtCore.QCoreApplication.instance() is None:
|
|
87
|
+
_qt_app = QtCore.QCoreApplication([])
|
|
88
|
+
sock = QtNetwork.QLocalSocket()
|
|
89
|
+
sock.connectToServer(name)
|
|
90
|
+
try:
|
|
91
|
+
if not sock.waitForConnected(int(timeout * 1000)):
|
|
92
|
+
return False
|
|
93
|
+
sock.write(SHOW_COMMAND)
|
|
94
|
+
if not sock.waitForBytesWritten(int(timeout * 1000)):
|
|
95
|
+
return False
|
|
96
|
+
sock.disconnectFromServer()
|
|
97
|
+
return True
|
|
98
|
+
finally:
|
|
99
|
+
sock.abort()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def notify_gui(server_name: str | None = None, *, timeout: float = 1.0) -> bool:
|
|
103
|
+
"""Deliver a show request to a running GUI. Return True if delivered."""
|
|
104
|
+
name = get_server_name(server_name)
|
|
105
|
+
if os.name == "posix":
|
|
106
|
+
if _notify_via_unix_socket(name, timeout):
|
|
107
|
+
return True
|
|
108
|
+
elif os.name == "nt":
|
|
109
|
+
if _notify_via_windows_pipe(name):
|
|
110
|
+
return True
|
|
111
|
+
try:
|
|
112
|
+
return _notify_via_qlocalsocket(name, timeout)
|
|
113
|
+
except ImportError:
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def default_launch_cmd() -> list[str] | None:
|
|
118
|
+
configured = os.environ.get(ENV_LAUNCH_COMMAND, "").strip()
|
|
119
|
+
if configured:
|
|
120
|
+
return shlex.split(configured, posix=(os.name != "nt"))
|
|
121
|
+
for script in ("caenhv-client-gui", "caenhv-client"):
|
|
122
|
+
found = shutil.which(script)
|
|
123
|
+
if found:
|
|
124
|
+
return [found]
|
|
125
|
+
try:
|
|
126
|
+
import caenhv_client # noqa: F401 (source/pip install present)
|
|
127
|
+
except ImportError:
|
|
128
|
+
return None
|
|
129
|
+
return [sys.executable, "-m", "caenhv_client"]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def default_popen_kwargs() -> dict:
|
|
133
|
+
kwargs: dict = {
|
|
134
|
+
"stdin": subprocess.DEVNULL,
|
|
135
|
+
"stdout": subprocess.DEVNULL,
|
|
136
|
+
"stderr": subprocess.DEVNULL,
|
|
137
|
+
"close_fds": True,
|
|
138
|
+
}
|
|
139
|
+
if os.name == "nt":
|
|
140
|
+
kwargs["creationflags"] = (
|
|
141
|
+
subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
kwargs["start_new_session"] = True
|
|
145
|
+
return kwargs
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def fire_gui(
|
|
149
|
+
server_name: str | None = None,
|
|
150
|
+
*,
|
|
151
|
+
launch_cmd: list[str] | None = None,
|
|
152
|
+
connect_timeout: float = 1.0,
|
|
153
|
+
launch_timeout: float = 15.0,
|
|
154
|
+
) -> str:
|
|
155
|
+
"""Raise the GUI if running, otherwise launch it detached.
|
|
156
|
+
|
|
157
|
+
Returns "raised" or "launched". Raises TimeoutError if a freshly
|
|
158
|
+
launched GUI does not start listening within launch_timeout, and
|
|
159
|
+
RuntimeError if no launch command can be determined.
|
|
160
|
+
"""
|
|
161
|
+
if notify_gui(server_name, timeout=connect_timeout):
|
|
162
|
+
return "raised"
|
|
163
|
+
cmd = launch_cmd or default_launch_cmd()
|
|
164
|
+
if not cmd:
|
|
165
|
+
raise RuntimeError(
|
|
166
|
+
"caenhv-client GUI is not running and no launch command was found; "
|
|
167
|
+
f"set {ENV_LAUNCH_COMMAND} to the caenhv-client executable path"
|
|
168
|
+
)
|
|
169
|
+
subprocess.Popen(cmd, **default_popen_kwargs())
|
|
170
|
+
deadline = time.monotonic() + launch_timeout
|
|
171
|
+
while time.monotonic() < deadline:
|
|
172
|
+
if notify_gui(server_name, timeout=connect_timeout):
|
|
173
|
+
return "launched"
|
|
174
|
+
time.sleep(0.25)
|
|
175
|
+
raise TimeoutError(f"caenhv-client GUI did not start within {launch_timeout} s (cmd: {cmd})")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: caenhv-client-python
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python interface to the caenhv-client GUI: fire/raise the application from other Python projects (e.g. labscript BLACS)
|
|
5
|
+
Author: Kenji Shu
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/kenji0923/caenhv-client
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# caenhv-client-python
|
|
16
|
+
|
|
17
|
+
Python interface to the [caenhv-client](https://github.com/kenji0923/caenhv-client)
|
|
18
|
+
GUI (CAEN HV control with channel linking). Zero dependencies; the GUI itself
|
|
19
|
+
is distributed as a standalone executable.
|
|
20
|
+
|
|
21
|
+
The only capability is *firing* the GUI — raise its window if running,
|
|
22
|
+
launch it otherwise — over a tiny local IPC protocol. There is deliberately
|
|
23
|
+
no remote control of HV settings.
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from caenhv_client_python import fire_gui, notify_gui
|
|
27
|
+
|
|
28
|
+
fire_gui() # raise the window if running, otherwise launch the GUI
|
|
29
|
+
notify_gui() # raise only; returns False if the GUI is not running
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Configuration via environment variables:
|
|
33
|
+
|
|
34
|
+
- `CAENHV_CLIENT_COMMAND` — path (or command line) of the caenhv-client
|
|
35
|
+
executable, used when it is not on `PATH`.
|
|
36
|
+
- `CAENHV_CLIENT_IPC_NAME` — IPC server name (default `caenhv-client`),
|
|
37
|
+
must match the GUI's setting when overridden.
|
|
38
|
+
|
|
39
|
+
Works from any Python process, including labscript BLACS tabs and workers;
|
|
40
|
+
no Qt required (a Unix-socket / named-pipe transport is used, with PyQt5 as
|
|
41
|
+
an optional fallback).
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
caenhv_client_python/__init__.py,sha256=6t0TMKB6bOuxcZZapK8h3FdewZabN-oPhUCHQph_zaQ,5561
|
|
2
|
+
caenhv_client_python-0.1.0.dist-info/licenses/LICENSE,sha256=P8fFayUDYkFlhTX_KIfX0U24GEQd15fdknSBFrc08NM,1066
|
|
3
|
+
caenhv_client_python-0.1.0.dist-info/METADATA,sha256=wSTRCrXcoc6igDAekR4BdpkF5j07L0d0UtCMejF2KtU,1593
|
|
4
|
+
caenhv_client_python-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
5
|
+
caenhv_client_python-0.1.0.dist-info/top_level.txt,sha256=y94CpCUafjoTPFzIkIGR4zVD1GgoYyqSaxjMcsV6Q4k,21
|
|
6
|
+
caenhv_client_python-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kenji Shu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
caenhv_client_python
|