digilent 0.17.4__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.
- digilent/__init__.py +0 -0
- digilent/cgse_explore.py +19 -0
- digilent/cgse_services.py +59 -0
- digilent/settings.yaml +23 -0
- digilent-0.17.4.dist-info/METADATA +23 -0
- digilent-0.17.4.dist-info/RECORD +16 -0
- digilent-0.17.4.dist-info/WHEEL +4 -0
- digilent-0.17.4.dist-info/entry_points.txt +15 -0
- egse/digilent/digilent.py +2809 -0
- egse/digilent/measurpoint/digilent_devif.py +278 -0
- egse/digilent/measurpoint/dt8874/__init__.py +21 -0
- egse/digilent/measurpoint/dt8874/dt8874.py +71 -0
- egse/digilent/measurpoint/dt8874/dt8874.yaml +43 -0
- egse/digilent/measurpoint/dt8874/dt8874_cs.py +335 -0
- egse/digilent/measurpoint/dt8874/dt8874_devif.py +14 -0
- egse/digilent/measurpoint/dt8874/dt8874_protocol.py +148 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import socket
|
|
3
|
+
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from egse.device import DeviceConnectionError
|
|
7
|
+
from egse.device import DeviceConnectionInterface
|
|
8
|
+
from egse.device import DeviceError
|
|
9
|
+
from egse.device import DeviceTimeoutError
|
|
10
|
+
from egse.device import DeviceTransport
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
IDENTIFICATION_QUERY = "*IDN?"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DigilentEthernetInterface(DeviceConnectionInterface, DeviceTransport):
|
|
18
|
+
"""Defines the low-level interface to the Digilent Controller."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, hostname: str, port: int, device_name: str, read_timeout: float = 60):
|
|
21
|
+
"""Initialisation of an Ethernet interface for a Digilent.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
hostname(str): Hostname to which to open a socket.
|
|
25
|
+
port (int): Port to which to open a socket.
|
|
26
|
+
device_name (str): Device name to which to open a socket.
|
|
27
|
+
read_timeout (float): Timeout for reading commands [s].
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
super().__init__()
|
|
31
|
+
|
|
32
|
+
self.hostname = hostname
|
|
33
|
+
self.port = port
|
|
34
|
+
self._sock = None
|
|
35
|
+
|
|
36
|
+
self._is_connection_open = False
|
|
37
|
+
|
|
38
|
+
self.device_name = device_name
|
|
39
|
+
self.read_timeout = read_timeout
|
|
40
|
+
|
|
41
|
+
def connect(self) -> None:
|
|
42
|
+
"""Connects to the Digilent hardware.
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
DeviceConnectionError: When the connection could not be established. Check the logging messages for more
|
|
46
|
+
details.
|
|
47
|
+
DeviceTimeoutError: When the connection timed out.
|
|
48
|
+
ValueError: When hostname or port number are not provided.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
# Sanity checks
|
|
52
|
+
|
|
53
|
+
if self._is_connection_open:
|
|
54
|
+
logger.warning(f"{self.device_name}: trying to connect to an already connected socket.")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
if self.hostname in (None, ""):
|
|
58
|
+
raise ValueError(f"{self.device_name}: hostname is not initialised.")
|
|
59
|
+
|
|
60
|
+
if self.port in (None, 0):
|
|
61
|
+
raise ValueError(f"{self.device_name}: port number is not initialised.")
|
|
62
|
+
|
|
63
|
+
# Create a new socket instance
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
67
|
+
# The following lines are to experiment with blocking and timeout, but there is no need.
|
|
68
|
+
# self._sock.setblocking(1)
|
|
69
|
+
# self._sock.settimeout(3)
|
|
70
|
+
except socket.error as e_socket:
|
|
71
|
+
raise DeviceConnectionError(self.device_name, "Failed to create socket.") from e_socket
|
|
72
|
+
|
|
73
|
+
# Attempt to establish a connection to the remote host
|
|
74
|
+
|
|
75
|
+
# FIXME: Socket shall be closed on exception?
|
|
76
|
+
|
|
77
|
+
# We set a timeout of 3s before connecting and reset to None (=blocking) after the `connect` method has been
|
|
78
|
+
# called. This is because when no device is available, e.g. during testing, the timeout will take about
|
|
79
|
+
# two minutes, which is way too long. It needs to be evaluated if this approach is acceptable and not causing
|
|
80
|
+
# problems during production.
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
logger.debug(f'Connecting a socket to host "{self.hostname}" using port {self.port}')
|
|
84
|
+
self._sock.settimeout(3)
|
|
85
|
+
self._sock.connect((self.hostname, self.port))
|
|
86
|
+
self._sock.settimeout(None)
|
|
87
|
+
except ConnectionRefusedError as exc:
|
|
88
|
+
raise DeviceConnectionError(
|
|
89
|
+
self.device_name, f"Connection refused to {self.hostname}:{self.port}."
|
|
90
|
+
) from exc
|
|
91
|
+
except TimeoutError as exc:
|
|
92
|
+
raise DeviceTimeoutError(self.device_name, f"Connection to {self.hostname}:{self.port} timed out.") from exc
|
|
93
|
+
except socket.gaierror as exc:
|
|
94
|
+
raise DeviceConnectionError(self.device_name, f"Socket address info error for {self.hostname}") from exc
|
|
95
|
+
except socket.herror as exc:
|
|
96
|
+
raise DeviceConnectionError(self.device_name, f"Socket host address error for {self.hostname}") from exc
|
|
97
|
+
except OSError as exc:
|
|
98
|
+
raise DeviceConnectionError(self.device_name, f"OSError caught ({exc}).") from exc
|
|
99
|
+
|
|
100
|
+
self._is_connection_open = True
|
|
101
|
+
|
|
102
|
+
# Check that we are connected to the controller by issuing the "VERSION" or
|
|
103
|
+
# "*ISDN?" query. If we don't get the right response, then disconnect automatically.
|
|
104
|
+
|
|
105
|
+
if not self.is_connected():
|
|
106
|
+
raise DeviceConnectionError(
|
|
107
|
+
self.device_name, "Device is not connected, check logging messages for the cause."
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def disconnect(self) -> None:
|
|
111
|
+
"""Disconnects from the Digilent hardware.
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
DeviceConnectionError when the socket could not be closed.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
if self._is_connection_open:
|
|
119
|
+
logger.debug(f"Disconnecting from {self.hostname}")
|
|
120
|
+
self._sock.close()
|
|
121
|
+
self._is_connection_open = False
|
|
122
|
+
except Exception as e_exc:
|
|
123
|
+
raise DeviceConnectionError(self.device_name, f"Could not close socket to {self.hostname}") from e_exc
|
|
124
|
+
|
|
125
|
+
def reconnect(self):
|
|
126
|
+
"""Reconnects to the Digilent hardware.
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
ConnectionError when the device cannot be reconnected for some reason.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
if self._is_connection_open:
|
|
133
|
+
self.disconnect()
|
|
134
|
+
self.connect()
|
|
135
|
+
|
|
136
|
+
def is_connected(self) -> bool:
|
|
137
|
+
"""Checks if the Digilent hardware is connected.
|
|
138
|
+
|
|
139
|
+
This will send a query for the device identification and validate the answer.
|
|
140
|
+
|
|
141
|
+
Returns: True is the device is connected and answered with the proper ID; False otherwise.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
if not self._is_connection_open:
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
print(f"Result from identification query: {self.query(IDENTIFICATION_QUERY)}")
|
|
149
|
+
# noinspection PyTypeChecker
|
|
150
|
+
manufacturer, model, _, _ = self.query(IDENTIFICATION_QUERY).split(",")
|
|
151
|
+
|
|
152
|
+
except DeviceError as exc:
|
|
153
|
+
logger.exception(exc)
|
|
154
|
+
logger.error("Most probably the client connection was closed. Disconnecting...")
|
|
155
|
+
self.disconnect()
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
if "Data Translation" not in manufacturer or "DT" not in model:
|
|
159
|
+
logger.error(
|
|
160
|
+
f'Device did not respond correctly to a "IDN?" command, manufacturer={manufacturer}, model={model}. Disconnecting...'
|
|
161
|
+
)
|
|
162
|
+
self.disconnect()
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
return True
|
|
166
|
+
|
|
167
|
+
def write(self, command: str) -> None:
|
|
168
|
+
"""Senda a single command to the device controller without waiting for a response.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
command (str): Command to send to the controller
|
|
172
|
+
|
|
173
|
+
Raises:
|
|
174
|
+
DeviceConnectionError when the command could not be sent due to a communication problem.
|
|
175
|
+
DeviceTimeoutError when the command could not be sent due to a timeout.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
command += "\n" if not command.endswith("\n") else ""
|
|
180
|
+
|
|
181
|
+
self._sock.sendall(command.encode())
|
|
182
|
+
|
|
183
|
+
except socket.timeout as e_timeout:
|
|
184
|
+
raise DeviceTimeoutError(self.device_name, "Socket timeout error") from e_timeout
|
|
185
|
+
except socket.error as e_socket:
|
|
186
|
+
# Interpret any socket-related error as a connection error
|
|
187
|
+
raise DeviceConnectionError(self.device_name, "Socket communication error.") from e_socket
|
|
188
|
+
except AttributeError:
|
|
189
|
+
if not self._is_connection_open:
|
|
190
|
+
msg = "The DAQ6510 is not connected, use the connect() method."
|
|
191
|
+
raise DeviceConnectionError(self.device_name, msg)
|
|
192
|
+
raise
|
|
193
|
+
|
|
194
|
+
def trans(self, command: str) -> str | bytes:
|
|
195
|
+
"""Sends a single command to the device controller and block until a response from the controller.
|
|
196
|
+
|
|
197
|
+
This is seen as a transaction.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
command (str): Command to send to the controller
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Either a string returned by the controller (on success), or an error message (on failure).
|
|
204
|
+
|
|
205
|
+
Raises:
|
|
206
|
+
DeviceConnectionError when there was an I/O problem during communication with the controller.
|
|
207
|
+
DeviceTimeoutError when there was a timeout in either sending the command or receiving the response.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
# Attempt to send the complete command
|
|
212
|
+
|
|
213
|
+
command += "\n" if not command.endswith("\n") else ""
|
|
214
|
+
|
|
215
|
+
self._sock.sendall(command.encode())
|
|
216
|
+
|
|
217
|
+
# wait for, read and return the response from HUBER (will be at most TBD chars)
|
|
218
|
+
|
|
219
|
+
return_string = self.read()
|
|
220
|
+
|
|
221
|
+
return return_string.decode().rstrip()
|
|
222
|
+
|
|
223
|
+
except UnicodeError:
|
|
224
|
+
# noinspection PyUnboundLocalVariable
|
|
225
|
+
return return_string
|
|
226
|
+
except socket.timeout as e_timeout:
|
|
227
|
+
raise DeviceTimeoutError(self.device_name, "Socket timeout error") from e_timeout
|
|
228
|
+
except socket.error as e_socket:
|
|
229
|
+
# Interpret any socket-related error as an I/O error
|
|
230
|
+
raise DeviceConnectionError(self.device_name, "Socket communication error.") from e_socket
|
|
231
|
+
except ConnectionError as exc:
|
|
232
|
+
raise DeviceConnectionError(self.device_name, "Connection error.") from exc
|
|
233
|
+
except AttributeError:
|
|
234
|
+
if not self._is_connection_open:
|
|
235
|
+
raise DeviceConnectionError(self.device_name, "Device not connected, use the connect() method.")
|
|
236
|
+
raise
|
|
237
|
+
|
|
238
|
+
def read(self) -> bytes:
|
|
239
|
+
"""Reads from the device buffer.
|
|
240
|
+
|
|
241
|
+
Returns: Content of the device buffer.
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
n_total = 0
|
|
245
|
+
buf_size = 2048
|
|
246
|
+
|
|
247
|
+
# Set a timeout of READ_TIMEOUT to the socket.recv
|
|
248
|
+
|
|
249
|
+
saved_timeout = self._sock.gettimeout()
|
|
250
|
+
self._sock.settimeout(self.read_timeout)
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
for idx in range(100):
|
|
254
|
+
time.sleep(0.001) # Give the device time to fill the buffer
|
|
255
|
+
data = self._sock.recv(buf_size)
|
|
256
|
+
n = len(data)
|
|
257
|
+
n_total += n
|
|
258
|
+
if n < buf_size:
|
|
259
|
+
break
|
|
260
|
+
except socket.timeout:
|
|
261
|
+
logger.warning(f"Socket timeout error for {self.hostname}:{self.port}")
|
|
262
|
+
return b"\r\n"
|
|
263
|
+
except TimeoutError as exc:
|
|
264
|
+
logger.warning(f"Socket timeout error: {exc}")
|
|
265
|
+
return b"\r\n"
|
|
266
|
+
finally:
|
|
267
|
+
self._sock.settimeout(saved_timeout)
|
|
268
|
+
|
|
269
|
+
# noinspection PyUnboundLocalVariable
|
|
270
|
+
return data
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def main():
|
|
274
|
+
return 0
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
if __name__ == "__main__":
|
|
278
|
+
main()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from egse.settings import Settings
|
|
4
|
+
|
|
5
|
+
HERE = Path(__file__).parent
|
|
6
|
+
settings = Settings.load("Digilent MEASURpoint DT8874 Control Server")
|
|
7
|
+
|
|
8
|
+
# General information about the Digilent MEASURpoint DT8874 Control Server
|
|
9
|
+
|
|
10
|
+
PROCESS_NAME = settings.get("PROCESS_NAME", "dt8874_cs") # Name under which it is registered in the service registry
|
|
11
|
+
SERVICE_TYPE = settings.get(
|
|
12
|
+
"SERVICE_TYPE", "dt8874_cs"
|
|
13
|
+
) # Service type under which it is registered in the service registry
|
|
14
|
+
PROTOCOL = settings.get("PROTOCOL", "tcp") # Communication protocol
|
|
15
|
+
HOSTNAME = settings.get("HOSTNAME", "localhost") # Hostname
|
|
16
|
+
COMMANDING_PORT = settings.get("COMMANDING_PORT", 0) # Commanding port (as per settings or dynamically assigned)
|
|
17
|
+
SERVICE_PORT = settings.get("SERVICE_PORT", 0) # Service port (as per settings or dynamically assigned)
|
|
18
|
+
MONITORING_PORT = settings.get("MONITORING_PORT", 0) # Monitoring port (as per settings or dynamically assigned)
|
|
19
|
+
ORIGIN = settings.get("STORAGE_MNEMONIC", "DT8874") # Storage mnemonic (used in the HK filenames)
|
|
20
|
+
|
|
21
|
+
PROXY_TIMEOUT = 10
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Digilent MEASURPOINT DT8874 commanding."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from egse.digilent.digilent import DigilentController, DigilentInterface
|
|
6
|
+
from egse.digilent.measurpoint.dt8874 import COMMANDING_PORT, PROTOCOL, HOSTNAME, SERVICE_TYPE, PROXY_TIMEOUT
|
|
7
|
+
from egse.digilent.measurpoint.dt8874.dt8874_devif import Dt8874EthernetInterface
|
|
8
|
+
from egse.proxy import DynamicProxy
|
|
9
|
+
from egse.registry.client import RegistryClient
|
|
10
|
+
from egse.zmq_ser import connect_address
|
|
11
|
+
|
|
12
|
+
LOGGER = logging.getLogger("egse.digilent.measurpoint.dt8874")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Dt8874Interface(DigilentInterface):
|
|
16
|
+
"""Base class for Digilent MEASURpoint DT8874 instruments."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Dt8874Controller(DigilentController, Dt8874Interface):
|
|
20
|
+
def __init__(self):
|
|
21
|
+
"""Initialisation of an Ariel TCU controller."""
|
|
22
|
+
|
|
23
|
+
super().__init__()
|
|
24
|
+
|
|
25
|
+
self.transport = self.dt8874 = Dt8874EthernetInterface()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Dt8874Simulator(Dt8874Interface):
|
|
29
|
+
def __init__(self):
|
|
30
|
+
"""Initialisation of a Digilent MEASURpoint DT8874 simulator."""
|
|
31
|
+
|
|
32
|
+
super().__init__()
|
|
33
|
+
|
|
34
|
+
self._is_pwd_protected_cmds_enabled = True
|
|
35
|
+
|
|
36
|
+
def enable_pwd_protected_cmds(self, password: str):
|
|
37
|
+
self._is_pwd_protected_cmds_enabled = True
|
|
38
|
+
|
|
39
|
+
def disable_pwd_protected_cmds(self, password: str):
|
|
40
|
+
self._is_pwd_protected_cmds_enabled = False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Dt8874Proxy(DynamicProxy, Dt8874Interface):
|
|
44
|
+
"""
|
|
45
|
+
The Dt8874Proxy class is used to connect to the Digilent MEASURpoint DT8874 Control Server and send commands to
|
|
46
|
+
the Digilent MEASURpoint DT8874 Hardware Controller remotely.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self):
|
|
50
|
+
"""Initialisation of a Dt8874Proxy."""
|
|
51
|
+
|
|
52
|
+
# Fixed ports -> Use information from settings
|
|
53
|
+
|
|
54
|
+
if COMMANDING_PORT != 0:
|
|
55
|
+
super().__init__(connect_address(PROTOCOL, HOSTNAME, COMMANDING_PORT))
|
|
56
|
+
|
|
57
|
+
# Dynamic port allocation -> Use Registry Client
|
|
58
|
+
|
|
59
|
+
else:
|
|
60
|
+
with RegistryClient() as reg:
|
|
61
|
+
service = reg.discover_service(SERVICE_TYPE)
|
|
62
|
+
|
|
63
|
+
if service:
|
|
64
|
+
protocol = service.get("protocol", "tcp")
|
|
65
|
+
hostname = service["host"]
|
|
66
|
+
port = service["port"]
|
|
67
|
+
|
|
68
|
+
super().__init__(connect_address(protocol, hostname, port), timeout=PROXY_TIMEOUT)
|
|
69
|
+
|
|
70
|
+
else:
|
|
71
|
+
raise RuntimeError(f"No service registered as {SERVICE_TYPE}")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
BaseClass:
|
|
2
|
+
egse.digilent.measurpoint.dt8874.Dt8874Interface
|
|
3
|
+
|
|
4
|
+
ProxyClass:
|
|
5
|
+
egse.digilent.measurpoint.dt8874.Dt8874Proxy
|
|
6
|
+
|
|
7
|
+
ControlServerClass:
|
|
8
|
+
egse.digilent.measurpoint.dt8874.Dt8874ControlServer
|
|
9
|
+
|
|
10
|
+
ControlServer:
|
|
11
|
+
egse.digilent.measurpoint.dt8874.dt8874_cs
|
|
12
|
+
|
|
13
|
+
#UserInterface:
|
|
14
|
+
# egse.digilent.measurpoint.dt8874.dt8874_ui
|
|
15
|
+
|
|
16
|
+
Commands:
|
|
17
|
+
|
|
18
|
+
# Definition of the DeviceInterface
|
|
19
|
+
|
|
20
|
+
is_simulator:
|
|
21
|
+
description: Ask if the connected class is a simulator instead of the real device Controller class.
|
|
22
|
+
returns: bool | True if the far end is a simulator instead of the real hardware
|
|
23
|
+
|
|
24
|
+
is_connected:
|
|
25
|
+
description: Check if the Digilent MEASURpoint DT8874 hardware controller is connected.
|
|
26
|
+
|
|
27
|
+
connect:
|
|
28
|
+
description: Connect the Digilent MEASURpoint DT8874 hardware controller
|
|
29
|
+
|
|
30
|
+
reconnect:
|
|
31
|
+
description: Reconnect the Digilent MEASURpoint DT8874 hardware controller.
|
|
32
|
+
|
|
33
|
+
This command will force a disconnect and then try to re-connect to the controller.
|
|
34
|
+
|
|
35
|
+
disconnect:
|
|
36
|
+
description: Disconnect from the Digilent MEASURpoint DT8874 hardware controller.
|
|
37
|
+
|
|
38
|
+
This command will be send to the DT8874 Control Server which will then
|
|
39
|
+
disconnect from the hardware controller.
|
|
40
|
+
|
|
41
|
+
This command does not affect the ZeroMQ connection of the Proxy to the
|
|
42
|
+
control server. Use the service command `disconnect_cs()` to disconnect
|
|
43
|
+
from the control server.
|