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,335 @@
1
+ """Control Server for the Digilent MEASURpoint DT8874."""
2
+
3
+ import logging
4
+ import multiprocessing
5
+ from typing import Annotated, Callable
6
+
7
+ import rich
8
+ import sys
9
+ import typer
10
+ import zmq
11
+
12
+ from egse.control import is_control_server_active, ControlServer
13
+ from egse.digilent.measurpoint.dt8874 import (
14
+ PROCESS_NAME,
15
+ SERVICE_TYPE,
16
+ COMMANDING_PORT,
17
+ SERVICE_PORT,
18
+ MONITORING_PORT,
19
+ ORIGIN,
20
+ PROTOCOL,
21
+ HOSTNAME,
22
+ )
23
+ from egse.digilent.measurpoint.dt8874.dt8874 import Dt8874Proxy
24
+ from egse.registry.client import RegistryClient
25
+ from egse.services import ServiceProxy
26
+ from egse.settings import Settings
27
+ from egse.setup import load_setup
28
+ from egse.storage import store_housekeeping_information
29
+ from egse.zmq_ser import connect_address, get_port_number
30
+
31
+ logger = logging.getLogger("egse.digilent.measurpoint.dt8874")
32
+ DEVICE_SETTINGS = Settings.load("Digilent MEASURpoint DT8874")
33
+
34
+
35
+ def is_dt8874_cs_active(timeout: float = 0.5) -> bool:
36
+ """Checks whether the Digilent MEASURpoint DT8874 Control Server is running.
37
+
38
+ Args:
39
+ timeout (float): Timeout when waiting for a reply [s].
40
+
41
+ Returns:
42
+ True if the Digilent MEASURpoint DT8874 Control Server is running and replied with the expected answer; False
43
+ otherwise.
44
+ """
45
+
46
+ if COMMANDING_PORT != 0:
47
+ protocol = PROTOCOL
48
+ hostname = HOSTNAME
49
+ port = COMMANDING_PORT
50
+
51
+ else:
52
+ with RegistryClient() as reg:
53
+ service = reg.discover_service(SERVICE_TYPE)
54
+
55
+ if service:
56
+ protocol = service.get("protocol", "tcp")
57
+ hostname = service["host"]
58
+ port = service["port"]
59
+
60
+ else:
61
+ return False
62
+
63
+ # noinspection PyUnboundLocalVariable
64
+ endpoint = connect_address(protocol, hostname, port)
65
+
66
+ return is_control_server_active(endpoint, timeout)
67
+
68
+
69
+ class Dt8874ControlServer(ControlServer):
70
+ def __init__(self, simulator: bool = False):
71
+ super().__init__()
72
+
73
+ multiprocessing.current_process().name = PROCESS_NAME
74
+
75
+ self.logger = logger
76
+ self.service_name = PROCESS_NAME
77
+ self.service_type = SERVICE_TYPE
78
+
79
+ from egse.digilent.measurpoint.dt8874.dt8874_protocol import Dt8874Protocol
80
+
81
+ self.device_protocol = Dt8874Protocol(self, simulator=simulator)
82
+
83
+ self.logger.info(f"Binding ZeroMQ socket to {self.device_protocol.get_bind_address()}")
84
+
85
+ self.device_protocol.bind(self.dev_ctrl_cmd_sock)
86
+
87
+ self.poller.register(self.dev_ctrl_cmd_sock, zmq.POLLIN)
88
+
89
+ self.register_service(SERVICE_TYPE)
90
+
91
+ def get_communication_protocol(self) -> str:
92
+ """Returns the communication protocol used by the Digilent MEASURpoint DT8874 Control Server.
93
+
94
+ Returns:
95
+ Communication protocol used by the Digilent MEASURpoint DT8874 Control Server, as specified in the settings.
96
+ """
97
+
98
+ return PROTOCOL
99
+
100
+ def get_commanding_port(self) -> int:
101
+ """Returns the commanding port used by the Digilent MEASURpoint DT8874 Control Server.
102
+
103
+ Returns:
104
+ Commanding port used by the Digilent MEASURpoint DT8874 Control Server, as specified in the settings.
105
+ """
106
+
107
+ return get_port_number(self.dev_ctrl_cmd_sock) or COMMANDING_PORT
108
+
109
+ def get_service_port(self) -> int:
110
+ """Returns the service port used by the Digilent MEASURpoint DT8874 Control Server.
111
+
112
+ Returns:
113
+ Service port used by the Digilent MEASURpoint DT8874 Control Server, as specified in the settings.
114
+ """
115
+
116
+ return get_port_number(self.dev_ctrl_service_sock) or SERVICE_PORT
117
+
118
+ def get_monitoring_port(self) -> int:
119
+ """Returns the monitoring port used by the Digilent MEASURpoint DT8874 Control Server.
120
+
121
+ Returns:
122
+ Monitoring port used by the Digilent MEASURpoint DT8874 Control Server, as specified in the settings.
123
+ """
124
+
125
+ return get_port_number(self.dev_ctrl_mon_sock) or MONITORING_PORT
126
+
127
+ def get_storage_mnemonic(self) -> str:
128
+ """Returns the storage mnemonic used by the Digilent MEASURpoint DT8874 Control Server.
129
+
130
+ Returns:
131
+ Storage mnemonic used by the Digilent MEASURpoint DT8874 Control Server, as specified in the settings.
132
+ """
133
+
134
+ return ORIGIN
135
+
136
+ def is_storage_manager_active(self):
137
+ """Checks whether the Storage Manager is active."""
138
+
139
+ from egse.storage import is_storage_manager_active
140
+
141
+ return is_storage_manager_active()
142
+
143
+ def store_housekeeping_information(self, data):
144
+ """Sends housekeeping information of the Digilent MEASURpoint DT8874 to the Storage Manager."""
145
+
146
+ origin = self.get_storage_mnemonic()
147
+ store_housekeeping_information(origin, data)
148
+
149
+ def register_to_storage_manager(self):
150
+ """Registers the Control Server to the Storage Manager."""
151
+
152
+ from egse.storage import register_to_storage_manager
153
+ from egse.storage.persistence import TYPES
154
+
155
+ register_to_storage_manager(
156
+ origin=self.get_storage_mnemonic(),
157
+ persistence_class=TYPES["CSV"],
158
+ prep={
159
+ "column_names": list(self.device_protocol.get_housekeeping().keys()),
160
+ "mode": "a",
161
+ },
162
+ )
163
+
164
+ def unregister_from_storage_manager(self):
165
+ """Unregisters the Control Server from the Storage Manager."""
166
+
167
+ from egse.storage import unregister_from_storage_manager
168
+
169
+ unregister_from_storage_manager(origin=self.get_storage_mnemonic())
170
+
171
+ def before_serve(self):
172
+ """Enables password-protected commands and applies the channel configuration.
173
+
174
+ Quite a lot of commands (e.g. configuring the channels and requesting measurement results) are
175
+ password-protected, so we need to enable those, before we can get going.
176
+
177
+ In the setup, we specify (under `setup.gse.dt8874`), which channels should be configured and how. It's also
178
+ those channels for which we request measurement results to populate the housekeeping and metrics.
179
+ """
180
+
181
+ try:
182
+ # Enable password-protected commands
183
+
184
+ password = DEVICE_SETTINGS.PASSWORD
185
+ self.device_protocol.dt8874.enable_pwd_protected_cmds(password=password)
186
+ except AttributeError as ae:
187
+ logger.warning(f"Couldn't enable password protected commands, check the log messages: {ae}.")
188
+
189
+ if self.device_protocol.dt8874.is_pwd_protected_cmds_enabled():
190
+ # Read the channel configuration from the setup + apply it to the device
191
+
192
+ self.device_protocol.dt8874.config_channels()
193
+
194
+ # start_http_server(CTRL_SETTINGS.METRICS_PORT)
195
+
196
+ def after_serve(self):
197
+ self.deregister_service()
198
+
199
+ def config_channels(self, event_data: dict):
200
+ """Configures the channels of the Digilent MEASURpoint DT8874 after a new setup has been loaded.
201
+
202
+ Args:
203
+ event_data (dict): Event data, containing the setup ID.
204
+ """
205
+
206
+ if data := event_data.get("data"):
207
+ if setup_id := data.get("setup_id"):
208
+ setup = load_setup(int(setup_id))
209
+ self.device_protocol.dt8874.config_channels(setup)
210
+
211
+ def get_event_handlers(self) -> dict[str, Callable]:
212
+ """Provides methods to handle the events the Control Server is subscribed to.
213
+
214
+ Returns:
215
+ Dictionary of event handlers.
216
+ """
217
+
218
+ return {"new_setup": self.config_channels}
219
+
220
+ def get_event_subscriptions(self) -> list[str]:
221
+ """Returns the list of events the Control Server is subscribed to.
222
+
223
+ Returns:
224
+ List of events the Control Server is subscribed to.
225
+ """
226
+
227
+ return ["new_setup"]
228
+
229
+
230
+ app = typer.Typer()
231
+
232
+
233
+ @app.command()
234
+ def start(
235
+ simulator: Annotated[
236
+ bool,
237
+ typer.Option(
238
+ "--simulator", "--sim", help="start the Digilent MEASURpoint DT8874 Control Server in simulator mode"
239
+ ),
240
+ ] = False,
241
+ ):
242
+ """Starts the Digilent MEASURpoint DT8874 Control Server."""
243
+
244
+ # noinspection PyBroadException
245
+ try:
246
+ controller = Dt8874ControlServer(simulator)
247
+ controller.serve()
248
+ except KeyboardInterrupt:
249
+ print("Shutdown requested...exiting")
250
+ except SystemExit as exc:
251
+ exit_code = exc.code if hasattr(exc, "code") else 0
252
+ print(f"System Exit with code {exc.code}")
253
+ sys.exit(exit_code)
254
+ except Exception:
255
+ logger.exception("Cannot start the Digilent MEASURpoint DT8874 Control Server")
256
+
257
+ return 0
258
+
259
+
260
+ @app.command()
261
+ def stop():
262
+ """Sends a `quit_server` command to the Digilent MEASURpoint DT8874 Control Server."""
263
+
264
+ with RegistryClient() as reg:
265
+ service = reg.discover_service(SERVICE_TYPE)
266
+
267
+ if service:
268
+ proxy = ServiceProxy(protocol="tcp", hostname=service["host"], port=service["metadata"]["service_port"])
269
+ proxy.quit_server()
270
+ else:
271
+ try:
272
+ with Dt8874Proxy() as dt8874_proxy:
273
+ with dt8874_proxy.get_service_proxy() as sp:
274
+ sp.quit_server()
275
+ except ConnectionError:
276
+ rich.print("[red]Couldn't connect to 'dt8874_cs', process probably not running. ")
277
+
278
+
279
+ @app.command()
280
+ def status():
281
+ """Requests the status information from the Digilent MEASURpoint DT8874 Control Server."""
282
+
283
+ if COMMANDING_PORT != 0:
284
+ endpoint = connect_address(PROTOCOL, HOSTNAME, COMMANDING_PORT)
285
+ port = COMMANDING_PORT
286
+ service_port = SERVICE_PORT
287
+ monitoring_port = MONITORING_PORT
288
+
289
+ else:
290
+ with RegistryClient() as reg:
291
+ service = reg.discover_service(SERVICE_TYPE)
292
+
293
+ if service:
294
+ protocol = service.get("protocol", "tcp")
295
+ hostname = service["host"]
296
+ port = service["port"]
297
+ service_port = service["metadata"]["service_port"]
298
+ monitoring_port = service["metadata"]["monitoring_port"]
299
+ endpoint = connect_address(protocol, hostname, port)
300
+ else:
301
+ rich.print(
302
+ f"[red]The Digilent MEASURpoint DT8874 Control Server isn't registered as a service. The Control "
303
+ f"Server cannot be contacted without the required information from the service registry.[/]"
304
+ )
305
+ rich.print("Digilent MEASURpoint DT8874: [red]not active")
306
+ return
307
+
308
+ # noinspection PyUnboundLocalVariable
309
+ if is_control_server_active(endpoint, timeout=2):
310
+ rich.print(f"Digilent MEASURpoint DT8874: [green]active -> {endpoint}")
311
+
312
+ with Dt8874Proxy() as dt8874:
313
+ sim = dt8874.is_simulator()
314
+ connected = dt8874.is_connected()
315
+ ip = dt8874.get_ip_address()
316
+ rich.print(f"mode: {'simulator' if sim else 'device'}{'' if connected else ' not'} connected")
317
+ rich.print(f"hostname: {ip}")
318
+ # noinspection PyUnboundLocalVariable
319
+ rich.print(f"commanding port: {port}")
320
+ # noinspection PyUnboundLocalVariable
321
+ rich.print(f"service port: {service_port}")
322
+ # noinspection PyUnboundLocalVariable
323
+ rich.print(f"monitoring port: {monitoring_port}")
324
+ else:
325
+ rich.print("Digilent MEASURpoint DT8874: [red]not active")
326
+
327
+
328
+ if __name__ == "__main__":
329
+ import logging
330
+
331
+ from egse.logger import set_all_logger_levels
332
+
333
+ set_all_logger_levels(logging.DEBUG)
334
+
335
+ sys.exit(app())
@@ -0,0 +1,14 @@
1
+ from egse.digilent.measurpoint.digilent_devif import DigilentEthernetInterface
2
+ from egse.settings import Settings
3
+
4
+ DEVICE_SETTINGS = Settings.load("Digilent MEASURpoint DT8874")
5
+
6
+
7
+ class Dt8874EthernetInterface(DigilentEthernetInterface):
8
+ def __init__(self, hostname: str = None, port: int = None):
9
+ hostname = DEVICE_SETTINGS.HOSTNAME if hostname is None else hostname
10
+ port = DEVICE_SETTINGS.PORT if port is None else port
11
+ device_name = DEVICE_SETTINGS.DEVICE_NAME
12
+ timeout = DEVICE_SETTINGS.TIMEOUT
13
+
14
+ super().__init__(hostname, port, device_name, timeout)
@@ -0,0 +1,148 @@
1
+ """Command protocol for the Digilent MEASURpoint DT8874."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+
6
+ from egse.command import ClientServerCommand
7
+ from egse.control import ControlServer
8
+ from egse.device import DeviceConnectionState
9
+ from egse.digilent.digilent import DigilentInterface
10
+ from egse.digilent.measurpoint.dt8874.dt8874 import Dt8874Simulator, Dt8874Controller
11
+ from egse.digilent.measurpoint.dt8874.dt8874_cs import ORIGIN
12
+ from egse.hk import read_conversion_dict, convert_hk_names
13
+ from egse.protocol import DynamicCommandProtocol
14
+ from egse.settings import Settings
15
+ from egse.system import format_datetime
16
+ from egse.zmq_ser import bind_address
17
+
18
+ _HERE = Path(__file__).parent
19
+ DEVICE_SETTINGS = Settings.load(filename="dt8874.yaml", location=_HERE)
20
+ LOGGER = logging.getLogger("egse.digilent.measurpoint.dt8874")
21
+
22
+
23
+ class Dt8874Command(ClientServerCommand):
24
+ """Command class for the Digilent MEASURpoint DT8874 Control Server."""
25
+
26
+ pass
27
+
28
+
29
+ class Dt8874Protocol(DynamicCommandProtocol):
30
+ """Command protocol for the Digilent MEASURpoint DT8874 Control Server."""
31
+
32
+ def __init__(self, control_server: ControlServer, simulator: bool = False):
33
+ """Initialisation of a Digilent MEASURpoint DT8874 protocol.
34
+
35
+ Args:
36
+ control_server (ControlServer): Digilent MEASURpoint DT8874 Control Server.
37
+ simulator (bool): Whether to use a simulator as the backend.
38
+ """
39
+
40
+ super().__init__(control_server)
41
+
42
+ self.hk_conversion_table = read_conversion_dict(
43
+ self.get_control_server().get_storage_mnemonic(), use_site=False
44
+ )
45
+
46
+ self.simulator = simulator
47
+
48
+ if self.simulator:
49
+ self.dt8874: DigilentInterface = Dt8874Simulator()
50
+ else:
51
+ self.dt8874: DigilentInterface = Dt8874Controller()
52
+
53
+ try:
54
+ self.dt8874.connect()
55
+ except ConnectionError:
56
+ LOGGER.warning("Couldn't establish connection to the Digilent MEASURpoint DT8874, check the log messages.")
57
+
58
+ # self.metrics = define_metrics("DT8874")
59
+
60
+ def get_bind_address(self) -> str:
61
+ """Returns the bind address for the Digilent MEASURpoint DT8874 Control Server.
62
+
63
+ Returns:
64
+ Bind address for the Digilent MEASURpoint DT8874 Control Server.
65
+ """
66
+
67
+ return bind_address(self.control_server.get_communication_protocol(), self.control_server.get_commanding_port())
68
+
69
+ def get_device(self) -> DigilentInterface:
70
+ """Returns the Digilent MEASURpoint DT8874 interface.
71
+
72
+ Returns:
73
+ Digilent MEASURpoint DT8874 interface.
74
+ """
75
+ return self.dt8874
76
+
77
+ def get_status(self) -> dict:
78
+ """Returns the status information for the Digilent MEASURpoint DT8874 Control Server.
79
+
80
+ Returns:
81
+ Status information for the Digilent MEASURpoint DT8874 Control Server.
82
+ """
83
+
84
+ status = super().get_status()
85
+
86
+ if self.state == DeviceConnectionState.DEVICE_NOT_CONNECTED and not self.simulator:
87
+ return status
88
+
89
+ # TODO Add device-specific status information
90
+
91
+ return status
92
+
93
+ def get_housekeeping(self) -> dict:
94
+ """Returns the housekeeping information for the Digilent MEASURpoint DT8874 Control Server.
95
+
96
+ Returns:
97
+ Housekeeping information for the Digilent MEASURpoint DT8874 Control Server.
98
+ """
99
+
100
+ result = dict()
101
+ result["timestamp"] = format_datetime()
102
+
103
+ if "RTD" in self.dt8874.channels:
104
+ for rtd_type in self.dt8874.channels.RTD:
105
+ rtd_temperatures = self.dt8874.get_rtd_temperature(
106
+ rtd_type=rtd_type, channels=self.dt8874.channels.RTD[rtd_type]
107
+ )
108
+
109
+ for channel_id, rtd_temperature in zip(self.dt8874.channel_lists.RTD[rtd_type], rtd_temperatures):
110
+ original_name = f"{ORIGIN}_T_RTD_{rtd_type}_CH{channel_id}"
111
+ result[original_name] = rtd_temperature
112
+
113
+ if "THERMOCOUPLE" in self.dt8874.channels:
114
+ for tc_type in self.dt8874.channels.THERMOCOUPLE:
115
+ tc_temperatures = self.dt8874.get_thermocouple_temperature(
116
+ tc_type=tc_type, channels=self.dt8874.channels.THERMOCOUPLE[tc_type]
117
+ )
118
+
119
+ for channel_id, tc_temperature in zip(self.dt8874.channel_lists.THERMOCOUPLE[tc_type], tc_temperatures):
120
+ original_name = f"{ORIGIN}_T_TC_{tc_type}_CH{channel_id}"
121
+ result[original_name] = tc_temperature
122
+
123
+ if "RESISTANCE" in self.dt8874.channels:
124
+ resistances = self.dt8874.get_resistance(self.dt8874.channels.RESISTANCE)
125
+
126
+ for channel_id, resistance in zip(self.dt8874.channel_lists.RESISTANCE, resistances):
127
+ original_name = f"{ORIGIN}_R_CH{channel_id}"
128
+ result[original_name] = resistance
129
+
130
+ if "VOLTAGE" in self.dt8874.channels:
131
+ voltages = self.dt8874.get_voltage(self.dt8874.channels.VOLTAGE)
132
+
133
+ for channel_id, voltage in zip(self.dt8874.channel_lists.VOLTAGE, voltages):
134
+ original_name = f"{ORIGIN}_V_CH{channel_id}"
135
+ result[original_name] = voltage
136
+
137
+ if self.hk_conversion_table:
138
+ return convert_hk_names(result, self.hk_conversion_table)
139
+ return result
140
+
141
+ def is_device_connected(self) -> bool:
142
+ """Checks whether the Digilent MEASURpoint DT8874 is connected.
143
+
144
+ Returns:
145
+ True if the Digilent MEASURpoint DT8874 is connected; False otherwise.
146
+ """
147
+
148
+ return self.dt8874.is_connected()