Skip to content

Commit 5195ad7

Browse files
heevastiHenri Ervasti
andauthored
Fix executable scripts in build (#170)
* [fix-executable-scripts-in-build] Working! But let's try to include the % sign in the coverage badge. * [fix-executable-scripts-in-build] Fix on build and executable script file names to make them work with with new `pyproject.toml` way of building. * small edits * [fix-executable-scripts-in-build] Fix on build and executable script file names to make them work with with new `pyproject.toml` way of building. Moved `qmi_tool` from "bin" scripts to executable tool scripts, like `qmi_proc`. * Update badges * [fix-executable-scripts-in-build] Bit more explanation on how `qmi_tool` works in docstring. * [fix-executable-scripts-in-build] Typo fix --------- Co-authored-by: Henri Ervasti <heevasti@tudelft.nl> Co-authored-by: Badge Bot <>
1 parent 2f82ab0 commit 5195ad7

File tree

10 files changed

+160
-134
lines changed

10 files changed

+160
-134
lines changed

.github/badges/pylint.svg

Lines changed: 6 additions & 6 deletions
Loading

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
- The Agiltron FF optical switch QMI drivers have now common base class in `qmi.instruments.agiltron._ff_optical_switch`
1616
- Refactored also unit-tests for Agiltron FF optical switches.
1717
- Zurich Instruments HDAWG instrument driver now uses `zhinst.core` instead of `zhinst.ziPython`. Also the 'schema' is obtained now from the instrument itself, and not from a separate file.
18+
- Changed `qmi_tool` to be also an executable script like `qmi_proc`.
1819

1920
### Fixed
2021
- In `usbtmc.py` now doing `.strip()` on `dev.serial_number` string to avoid SNs with whitespace character(s).
22+
- Fixed in `pyproject.toml` the executable `qmi_proc` and "adwin" scripts in "bin" to be in separate section w.r.t. the other scripts which should not be executable.
2123

2224
### Removed
2325
- Zurich Instruments HDAWG instrument driver `hdawg_command_table.schema` file.

bin/instruments/qmi_teraxion_tfn

100755100644
File mode changed.

bin/qmi_hdf5_to_mat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#! /usr/bin/env python
1+
#!/usr/bin/env python
22

33
import os
44
import argparse

bin/qmi_tool

Lines changed: 3 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,5 @@
1-
#! /usr/bin/env python
1+
#!/usr/bin/env python
22

3-
"""Swiss army knife for QMI command line monitoring and control."""
4-
5-
import random
63
import sys
7-
import socket
8-
import time
9-
import re
10-
11-
from qmi.core.config_defs import CfgQmi
12-
from qmi.core.context import ping_qmi_contexts
13-
from qmi.core.udp_responder_packets import QMI_UdpResponderKillRequestPacket
14-
15-
16-
UDP_RESPONDER_PORT = 35999
17-
18-
19-
def lsqmi(workgroup_name: str = CfgQmi.workgroup, timeout: float = 0.1) -> None:
20-
"""List all QMI contexts on the network.
21-
22-
Parameters:
23-
workgroup_name: The name of the workgroup to be searched for. Default is the CfgQmi.workgroup default.
24-
timeout: time to wait for answers (default: 0.1).
25-
"""
26-
# Ping and collect responses.
27-
timestamped_packets = ping_qmi_contexts(workgroup_name_filter=workgroup_name, timeout=timeout)
28-
29-
# Print info.
30-
print("Number of contexts found: {}".format(len(timestamped_packets)))
31-
32-
for (response_packet_received_timestamp, incoming_address, response_packet) in timestamped_packets:
33-
# Estimate, on our local clock, at what time the packet was processed on the other side.
34-
t_at_remote = 0.5 * (response_packet.request_pkt_timestamp + response_packet_received_timestamp) # average
35-
36-
# Determine clock deviation (their clock - our clock).
37-
clock_deviation = response_packet.pkt_timestamp - t_at_remote
38-
39-
# Roundtrip time.
40-
roundtrip_time = response_packet_received_timestamp - response_packet.request_pkt_timestamp
41-
42-
print("QMI_Context found: {}:{}, pid {!r}, name {!r}; workgroup {!r}; round-trip time {:.3f} ms; clock deviation {:+.3f} ms.".format(
43-
incoming_address[0],
44-
response_packet.context.port,
45-
response_packet.context.pid,
46-
response_packet.context.name.decode(),
47-
response_packet.context.workgroup_name.decode(),
48-
1e3 * roundtrip_time,
49-
1e3 * clock_deviation
50-
))
51-
52-
53-
def hard_kill() -> None:
54-
"""Broadcast a hard-kill request."""
55-
# Preparing outgoing socket.
56-
udp_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
57-
58-
try:
59-
# Allow broadcasts on the socket.
60-
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
61-
62-
# Send packet.
63-
address_out = ('<broadcast>', UDP_RESPONDER_PORT)
64-
request_pkt_id = random.randint(1, 2**64 - 1)
65-
request_packet = QMI_UdpResponderKillRequestPacket.create(
66-
request_pkt_id,
67-
time.time()
68-
)
69-
udp_socket.sendto(request_packet, address_out)
70-
71-
finally:
72-
udp_socket.close()
73-
74-
75-
def run() -> None | str:
76-
for e, arg in enumerate(sys.argv[1:]):
77-
if arg not in ["ls", "hard-kill", "hard-kill-yes-really-i-am-sure"]:
78-
return f"Invalid argument {arg}."
79-
80-
if arg == "ls":
81-
if len(sys.argv) == (e + 3):
82-
# we have one input and need to check if it is timeout or workgroup name
83-
if len(re.findall(r"[a-zA-z]+", sys.argv[e+2])) == 0:
84-
# no alphabet characters so it must be a number
85-
lsqmi(timeout=float(sys.argv[e+2]))
86-
else:
87-
lsqmi(sys.argv[e+2])
88-
89-
elif len(sys.argv) == (e + 4):
90-
# we have two inputs and need to check which is timeout and which workgroup name
91-
if len(re.findall(r"[a-zA-z]+", sys.argv[e + 2])) == 0:
92-
# no alphabet characters so it must be a number and second argument must be the workgroup name
93-
lsqmi(sys.argv[e + 3], timeout=float(sys.argv[e + 2]))
94-
else:
95-
lsqmi(sys.argv[e + 2], timeout=float(sys.argv[e + 3]))
96-
97-
else:
98-
# We use defaults
99-
lsqmi()
100-
101-
if arg == "hard-kill":
102-
print("This kills all the contexts that are visible (see output of `ls`)!")
103-
print("If you are sure, use `qmi_tool hard-kill-yes-really-i-am-sure`")
104-
105-
if arg == "hard-kill-yes-really-i-am-sure":
106-
hard_kill()
107-
108-
109-
if __name__ == "__main__":
110-
sys.exit(run())
4+
import qmi.tools.qmi_tool
5+
sys.exit(qmi.tools.qmi_tool.run())

pyproject.toml

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -79,22 +79,24 @@ dev = [
7979
qmi_proc = "qmi.tools.proc:run"
8080
qmi_adbasic_parser = "qmi.utils.adbasic_parser:run"
8181
qmi_adbasic_compiler = "qmi.utils.adbasic_compiler:run"
82-
# tool and instrument scripts
83-
qmi_tool = "bin.qmi_tool:run"
84-
qmi_hdf5_to_mat = "bin.qmi_hdf5_to_mat:run"
85-
qmi_anapico_apsin = "bin.instruments.qmi_anapico_apsin:run"
86-
qmi_highfinesse_ws = "bin.instruments.qmi_highfinesse_ws:run"
87-
qmi_mcc_usb1808x = "bin.instruments.qmi_mcc_usb1808x:run"
88-
qmi_newport_ag_uc8 = "bin.instruments.qmi_newport_ag_uc8:run"
89-
qmi_quantum_composer_9530 = "bin.instruments.qmi_quantum_composer_9530:run"
90-
qmi_siglent_ssa3000x = "bin.instruments.qmi_siglent_ssa3000x:run"
91-
qmi_srs_dc205 = "bin.instruments.qmi_srs_dc205:run"
92-
qmi_tenma_72psu = "bin.instruments.qmi_tenma_72psu:run"
93-
qmi_teraxion_tfn = "bin.instruments.qmi_teraxion_tfn:run"
94-
qmi_thorlabs_k10cr1 = "bin.instruments.qmi_thorlabs_k10cr1:run"
95-
qmi_timebase_dim3000 = "bin.instruments.qmi_timebase_dim3000:run"
96-
qmi_wavelength_tclab = "bin.instruments.qmi_wavelength_tclab:run"
97-
qmi_wieserlabs_flexdds = "bin.instruments.qmi_wieserlabs_flexdds:run"
82+
qmi_tool = "qmi.tools.qmi_tool:run"
9883

9984
[tool.setuptools]
10085
include-package-data = true
86+
# tool and instrument scripts
87+
script-files = [
88+
"bin/qmi_hdf5_to_mat",
89+
"bin/instruments/qmi_anapico_apsin",
90+
"bin/instruments/qmi_highfinesse_ws",
91+
"bin/instruments/qmi_mcc_usb1808x",
92+
"bin/instruments/qmi_newport_ag_uc8",
93+
"bin/instruments/qmi_quantum_composer_9530",
94+
"bin/instruments/qmi_siglent_ssa3000x",
95+
"bin/instruments/qmi_srs_dc205",
96+
"bin/instruments/qmi_tenma_72psu",
97+
"bin/instruments/qmi_teraxion_tfn",
98+
"bin/instruments/qmi_thorlabs_k10cr1",
99+
"bin/instruments/qmi_timebase_dim3000",
100+
"bin/instruments/qmi_wavelength_tclab",
101+
"bin/instruments/qmi_wieserlabs_flexdds",
102+
]

qmi/core/usbtmc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
- Always call set_configuration() to synchronize data toggle.
4343
- Clear Bulk-IN endpoint during initialization to synchronize data toggle.
4444
45-
Modification by HK Ervasti:
45+
Modifications by HK Ervasti:
4646
- .strip() `dev.serial_number` string to avoid SNs with whitespace character(s).
4747
- Logging import and a `_logger` instance with some 'debug' logging lines.
4848
"""

qmi/instruments/picoquant/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
22
PicoQuant, time tagger instruments and event handling.
33
4-
The qmi.instruments.pi package provides support for:
4+
The qmi.instruments.picoquant package provides support for:
55
- MultiHarp 150
66
- HydraHarp 400
77
- PicoHarp 300.

qmi/tools/proc.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
"""
22
# QMI Process Manager
33
4-
This tool starts or stops background QMI processes.
4+
This tool can start, stop and query background QMI processes. Processes are identified by their context name,
5+
as specified in the QMI configuration file. Processes can run either on the local computer or on a remote,
6+
network-connected computer.
7+
8+
When installing QMI with `pip`, also an executable `qmi_proc[.exe]` is created and can be used from command line.
9+
Run on command line `qmi_proc --help` to see usage options for `qmi_proc`.
510
"""
11+
612
import builtins
713
import sys
814
import argparse

qmi/tools/qmi_tool.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"""Swiss army knife for QMI command line monitoring and control.
2+
3+
Run with `qmi_tool ls` or `qmi_tool lsqmi` to list all QMI contexts present on the network with the default
4+
workgroup name ("default"). To see other contexts in other workgroups, use:
5+
`qmi_tool ls <workgroup-name>`.
6+
You can also give timeout:
7+
`qmi_tool ls <workgroup-name> 10`.
8+
9+
You can also use this tool to kill all visible QMI contexts with the default workgroup name with:
10+
`qmi_tool hard-kill`. Use with care and never use without first checking which contexts will be killed,
11+
with `qmi_tool ls`.
12+
"""
13+
14+
import random
15+
import sys
16+
import socket
17+
import time
18+
import re
19+
20+
from qmi.core.config_defs import CfgQmi
21+
from qmi.core.context import ping_qmi_contexts
22+
from qmi.core.udp_responder_packets import QMI_UdpResponderKillRequestPacket
23+
24+
25+
UDP_RESPONDER_PORT = 35999
26+
27+
28+
def lsqmi(workgroup_name: str = CfgQmi.workgroup, timeout: float = 0.1) -> None:
29+
"""List all QMI contexts on the network.
30+
31+
Parameters:
32+
workgroup_name: The name of the workgroup to be searched for. Default is the CfgQmi.workgroup default.
33+
timeout: Timeout to wait for answers (default: 0.1).
34+
"""
35+
# Ping and collect responses.
36+
timestamped_packets = ping_qmi_contexts(workgroup_name_filter=workgroup_name, timeout=timeout)
37+
38+
# Print info.
39+
print("Number of contexts found: {}".format(len(timestamped_packets)))
40+
41+
for (response_packet_received_timestamp, incoming_address, response_packet) in timestamped_packets:
42+
# Estimate, on our local clock, at what time the packet was processed on the other side.
43+
t_at_remote = 0.5 * (response_packet.request_pkt_timestamp + response_packet_received_timestamp) # average
44+
45+
# Determine clock deviation (their clock - our clock).
46+
clock_deviation = response_packet.pkt_timestamp - t_at_remote
47+
48+
# Roundtrip time.
49+
roundtrip_time = response_packet_received_timestamp - response_packet.request_pkt_timestamp
50+
51+
print("QMI_Context found: {}:{}, pid {!r}, name {!r}; workgroup {!r}; round-trip time {:.3f} ms; clock deviation {:+.3f} ms.".format(
52+
incoming_address[0],
53+
response_packet.context.port,
54+
response_packet.context.pid,
55+
response_packet.context.name.decode(),
56+
response_packet.context.workgroup_name.decode(),
57+
1e3 * roundtrip_time,
58+
1e3 * clock_deviation
59+
))
60+
61+
62+
def hard_kill() -> None:
63+
"""Broadcast a hard-kill request."""
64+
# Preparing outgoing socket.
65+
udp_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
66+
67+
try:
68+
# Allow broadcasts on the socket.
69+
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
70+
71+
# Send packet.
72+
address_out = ('<broadcast>', UDP_RESPONDER_PORT)
73+
request_pkt_id = random.randint(1, 2**64 - 1)
74+
request_packet = QMI_UdpResponderKillRequestPacket.create(
75+
request_pkt_id,
76+
time.time()
77+
)
78+
udp_socket.sendto(request_packet, address_out)
79+
80+
finally:
81+
udp_socket.close()
82+
83+
84+
def run() -> None | str:
85+
for e, arg in enumerate(sys.argv[1:]):
86+
if arg not in ["ls", "lsqmi", "hard-kill", "hard-kill-yes-really-i-am-sure"]:
87+
return f"Invalid argument {arg}."
88+
89+
if arg == "ls" or arg == "lsqmi":
90+
if len(sys.argv) == (e + 3):
91+
# we have one input and need to check if it is timeout or workgroup name
92+
if len(re.findall(r"[a-zA-z]+", sys.argv[e+2])) == 0:
93+
# no alphabet characters so it must be a number
94+
lsqmi(timeout=float(sys.argv[e+2]))
95+
else:
96+
lsqmi(sys.argv[e+2])
97+
98+
elif len(sys.argv) == (e + 4):
99+
# we have two inputs and need to check which is timeout and which workgroup name
100+
if len(re.findall(r"[a-zA-z]+", sys.argv[e + 2])) == 0:
101+
# no alphabet characters so it must be a number and second argument must be the workgroup name
102+
lsqmi(sys.argv[e + 3], timeout=float(sys.argv[e + 2]))
103+
else:
104+
lsqmi(sys.argv[e + 2], timeout=float(sys.argv[e + 3]))
105+
106+
else:
107+
# We use defaults
108+
lsqmi()
109+
110+
if arg == "hard-kill":
111+
print("This kills all the contexts that are visible (see output of `ls`)!")
112+
print("If you are sure, use `qmi_tool hard-kill-yes-really-i-am-sure`")
113+
114+
if arg == "hard-kill-yes-really-i-am-sure":
115+
hard_kill()
116+
117+
return None
118+
119+
120+
if __name__ == "__main__":
121+
sys.exit(run())

0 commit comments

Comments
 (0)