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.
@@ -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.