eaps2000 0.1.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,99 @@
1
+ Metadata-Version: 2.4
2
+ Name: eaps2000
3
+ Version: 0.1.4
4
+ Summary: Elektro-Automatik Series PS 2000 Python Controller
5
+ Project-URL: Homepage, https://github.com/KozhinovAlexander/eaps2000
6
+ Project-URL: Documentation, https://github.com/KozhinovAlexander/eaps2000/blob/main/README.md
7
+ Author-email: Alexander Kozhinov <ak.alexander.kozhinov@gmail.com>
8
+ License-Expression: Apache-2.0
9
+ Keywords: ds-power-supply,ea-ps-2042-06b,ea-ps-2042-10b,ea-ps-2042-20b,ea-ps-2084-03b,ea-ps-2084-05b,ea-ps-2342-06b,ea-ps-2342-10b,ea-ps-2384-05b,psu,psu-controller,pypi-package,python-psu-controller,series-ps-2000-b
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Programming Language :: Python
12
+ Requires-Python: >=3.8
13
+ Requires-Dist: pyserial>=3.5
14
+ Provides-Extra: dev
15
+ Requires-Dist: flake8>=7.1.2; extra == 'dev'
16
+ Requires-Dist: pytest-flake8>=1.3.0; extra == 'dev'
17
+ Requires-Dist: pytest>=6.2.2; extra == 'dev'
18
+ Requires-Dist: versioningit>=3.1.2; extra == 'dev'
19
+ Description-Content-Type: text/markdown
20
+
21
+ - [eaps2000 - PS 2000B Series PSU Python Control Unit](#eaps2000---ps-2000b-series-psu-python-control-unit)
22
+ - [Installing the package](#installing-the-package)
23
+ - [Getting Started](#getting-started)
24
+ - [Building the Project](#building-the-project)
25
+
26
+
27
+ # eaps2000 - PS 2000B Series PSU Python Control Unit
28
+
29
+ The `eaps2000` is a python module for [Elektro-Automatik PS 2000B Series][_ps_2kb_url_] PSU control.
30
+
31
+ This software implements following functionality:
32
+
33
+ - Reading out device information (serial number, model etc.)
34
+ - Setting ovewr-voltage and over-current protection
35
+ - Setting voltage and current for an output
36
+ - Controlling the output stage on/off
37
+ - Acknowledging alarms
38
+
39
+ ## Installing the package
40
+
41
+ Install the project from PyPi or [build](#building-the-project) it first.
42
+
43
+ ```bash
44
+ pip install eaps2000
45
+ ```
46
+
47
+ ## Getting Started
48
+
49
+ Using CLI interface:
50
+
51
+ ```bash
52
+ eaps2000 --help
53
+ ```
54
+
55
+ Using Python interface:
56
+
57
+ ```python
58
+ from eaps2000 import eaps2k
59
+
60
+ port = 'COM123' # use /tty/ACM0 for linux based system
61
+ with eaps2k(port) as ps:
62
+ # Prepare config:
63
+ cfg = eaps2k.get_config_template()
64
+ cfg['ACK'] = True # acknowledge alarms if any
65
+ cfg['OVP'] = 5.0 # over-voltage-protection value
66
+ cfg['OCP'] = 0.5 # over-current-protection value
67
+ cfg['Iset'] = 0.1 # current to be set
68
+ cfg['Vset'] = 3.3 # coltage to be set
69
+
70
+ # Turn off the output stage:
71
+ ps.set_output_state(False)
72
+
73
+ # Apply configuration:
74
+ ps.configure(cfg)
75
+
76
+ # Turn on the output stage:
77
+ # ATTENTION: The power will be applied to your probe here!
78
+ # ps.set_output_state(True)
79
+
80
+ # Show information:
81
+ ps.print_info()
82
+ ```
83
+
84
+ ## Building the Project
85
+
86
+ The project is built with [`hatchling`][_hatchling_home_]
87
+
88
+ ```bash
89
+ pip install hatchling && flake8 . -v && hatchling build && pytest --flake8
90
+ ```
91
+
92
+ Installing freshly built project may be done by invoking:
93
+
94
+ ```bash
95
+ pip install ./dist/eaps2000-*.whl --upgrade --force-reinstall
96
+ ```
97
+
98
+ [_ps_2kb_url_]: https://elektroautomatik.com/shop/de/produkte/programmierbare-dc-laborstromversorgungen/dc-laborstromversorgungen/serie-ps-2000-b-br-100-bis-332-w/
99
+ [_hatchling_home_]: https://hatch.pypa.io/1.9/
@@ -0,0 +1,5 @@
1
+ eaps2000.py,sha256=uniBkyTgJd8X9UXa1mQOqZTiQ2TMJs97vUVVhRrketw,23511
2
+ eaps2000-0.1.4.dist-info/METADATA,sha256=dMP_C3eNMPxDwBVgYRuEfFP99uCCvNVf5SktqH8NXbY,3145
3
+ eaps2000-0.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4
+ eaps2000-0.1.4.dist-info/entry_points.txt,sha256=JrFCoI1FpNHhV7IHgfpOi5kJYD03LECodYZx2DLjCnk,43
5
+ eaps2000-0.1.4.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ eaps2000 = eaps2000:main
eaps2000.py ADDED
@@ -0,0 +1,630 @@
1
+ #!/usr/bin/env python
2
+
3
+ '''
4
+ Elektro-Automatik Series PS 2000 Python Controller
5
+
6
+ License: Apache 2.0
7
+
8
+ Disclaimer: This is NOT an official software made by the manufacturer.
9
+
10
+ Description: The Elektro-Automatik Series PS 2000 Python Controller
11
+ is a versatile software designed to interface with the Elektro-Automatik
12
+ PS 2000 series power supplies.
13
+ Developed with Python, this controller allows users to automate and
14
+ manage their power supply operations efficiently.
15
+
16
+ IMPORTANT NOTICE: This software is provided as-is. There is NO WARRANTY,
17
+ NO GUARANTEE of performance, and it is NOT officially endorsed by the manufacturer.
18
+ Use at your own risk.
19
+
20
+ Author: Alexander Kozhinov <ak.alexander.kozhinov@gmail.com>
21
+ '''
22
+
23
+ import argparse
24
+ import serial
25
+ import struct
26
+ import sys
27
+ from importlib.metadata import version
28
+
29
+
30
+ class eaps2k(object):
31
+ PS_QUERY = 0x40
32
+ PS_SEND = 0xc0
33
+
34
+ def __init__(self, port: str, timeout: float = 0.06, baudrate: int = 115200,
35
+ parity: str = serial.PARITY_ODD, verbosity_level=0):
36
+ '''
37
+ Initialize the PS2000 device with the specified serial port settings.
38
+ Args:
39
+ port (str): The serial port to which the PS2000 device is connected.
40
+ timeout (float, optional): The timeout for serial communication in seconds. Default is 0.06.
41
+ baudrate (int, optional): The baud rate for serial communication. Default is 115200.
42
+ parity (str, optional): The parity bit setting for serial communication. Default is serial.PARITY_ODD.
43
+ verbosity_level (int, optional): The verbosity level for logging. Use 3 to see more information. Default is 0.
44
+ Attributes:
45
+ _verbose (int): Stores the verbosity level for logging.
46
+ ser_dev (serial.Serial): The serial device object for communication with the PS2000.
47
+ _u_nom (float): The nominal voltage of the PS2000 device.
48
+ _i_nom (float): The nominal current of the PS2000 device.
49
+ '''
50
+ self._verbosity_lvl = verbosity_level
51
+ # set timeout to 0.06s to guarantee minimum interval time of 50ms
52
+ self.ser_dev = serial.Serial(port, timeout=timeout, baudrate=baudrate,
53
+ parity=parity)
54
+ self._translation_factor = 25600.0
55
+ self._u_nom = self.get_nominal_voltage()
56
+ self._i_nom = self.get_nominal_current()
57
+
58
+ @staticmethod
59
+ def pkg_version() -> str:
60
+ '''
61
+ Returns this packgae version
62
+ '''
63
+ ver = '0.0.0'
64
+ try:
65
+ ver = version(__name__)
66
+ except Exception:
67
+ pass
68
+ return ver
69
+
70
+ @staticmethod
71
+ def description():
72
+ '''
73
+ Returns description of this module.
74
+ '''
75
+ descr = \
76
+ f'{__name__} {eaps2k.pkg_version()}. ' \
77
+ '\nElektro-Automatik Series PS 2000 Python Controller. ' \
78
+ '\nLicense: Apache 2.0 ' \
79
+ '\nDisclaimer: This is NOT an official software made by the manufacturer. ' \
80
+ '\nIMPORTANT NOTICE: This software is provided as-is. There is NO WARRANTY,' \
81
+ '\nNO GUARANTEE of performance, and it is NOT officially endorsed by the manufacturer. ' \
82
+ '\nUse at your own risk. ' \
83
+ '\nAuthor: Alexander Kozhinov <ak.alexander.kozhinov@gmail.com>'
84
+ return descr
85
+
86
+ def __enter__(self):
87
+ self.set_remote(True)
88
+ return self
89
+
90
+ def __exit__(self, exc_type, exc_value, traceback):
91
+ self.set_remote(False)
92
+ self.ser_dev.close()
93
+ if exc_type is not None:
94
+ print(f'An exception occurred: {exc_value}\nTraceback: {traceback}')
95
+
96
+ @staticmethod
97
+ def _construct_telegram(telegram_type, node, obj, data) -> bytearray:
98
+ '''
99
+ Constructs a telegram message for communication.
100
+ Args:
101
+ telegram_type (int): The type of the telegram.
102
+ node (int): The device node.
103
+ obj (int): The object identifier.
104
+ data (bytes): The data to be included in the telegram.
105
+ Returns:
106
+ bytearray: The constructed telegram message.
107
+ The telegram message format is as follows:
108
+ - Start delimiter (SD): 0x30 + telegram_type
109
+ - Device node (DN): node
110
+ - Object (OBJ): obj
111
+ - Data (DATA): data (if any)
112
+ - Checksum (CS): sum of all previous bytes, split into two bytes (CS0 and CS1)
113
+ '''
114
+ telegram = bytearray()
115
+ telegram.append(0x30 + telegram_type) # SD (start delimiter)
116
+ telegram.append(node) # DN (device node)
117
+ telegram.append(obj) # OBJ (object)
118
+ if len(data) > 0: # DATA
119
+ telegram.extend(data)
120
+ telegram[0] += len(data) - 1 # update length
121
+
122
+ cs = sum(telegram)
123
+ telegram.append(cs >> 8) # CS0
124
+ telegram.append(cs & 0xff) # CS1 (checksum)
125
+
126
+ return telegram
127
+
128
+ @staticmethod
129
+ def _checksum_verify(ans):
130
+ '''
131
+ Compare checksum with header and data in response from device.
132
+ This function calculates the checksum of the given response data and
133
+ compares it with the checksum provided in the last two bytes of the response.
134
+ If the calculated checksum does not match the provided checksum, an assertion
135
+ error is raised indicating a checksum mismatch.
136
+ Args:
137
+ ans (list or bytearray): The response data from the device, including the
138
+ checksum in the last two bytes.
139
+ Raises:
140
+ AssertionError: If the calculated checksum does not match the provided checksum.
141
+ '''
142
+ cs = sum(ans[0:-2])
143
+ assert ans[-2] == (cs >> 8) and ans[-1] == (cs & 0xff), \
144
+ 'ERROR: Checksum mismatch'
145
+
146
+ @staticmethod
147
+ def _check_error(ans):
148
+ '''
149
+ Checks for errors in the given answer and raises an assertion error with
150
+ a detailed message if an error is found.
151
+ Args:
152
+ ans (list of int): The answer to check, represented as a list of integers.
153
+ Raises:
154
+ AssertionError: If an error is found, with a message detailing the
155
+ error type and the answer in hexadecimal format.
156
+ '''
157
+ if ans[2] != 0xff:
158
+ return
159
+
160
+ response_state_table = {
161
+ 0x00: 'OK: Acknowledge', # got acknowledge - not an error
162
+ 0x03: 'ERROR: Com: Checksum incorrect',
163
+ 0x04: 'ERROR: Com: Start delimiter incorrect',
164
+ 0x05: 'ERROR: Com: Wrong address for output',
165
+ 0x07: 'ERROR: Com: Object not defined',
166
+ 0x08: 'ERROR: Usr: Object length incorrect',
167
+ 0x09: 'ERROR: Usr: Access denied',
168
+ 0x0f: 'ERROR: Usr: Device is locked',
169
+ 0x30: 'ERROR: Usr: Upper limit exceeded',
170
+ 0x31: 'ERROR: Usr: Lower limit exceeded',
171
+ }
172
+
173
+ resp_str = 'unknown error'
174
+ if ans[3] in response_state_table.keys():
175
+ resp_str = response_state_table[ans[3]]
176
+ assert ans[3] == 0x00, f'{resp_str}\n-- answer:\t\t{eaps2k.bytes2hex(ans)}'
177
+
178
+ @staticmethod
179
+ def bytes2hex(bytes_arr):
180
+ '''
181
+ Converts a byte array to a string of hexadecimal values.
182
+ Args:
183
+ bytes_arr (bytes): The byte array to convert.
184
+ Returns:
185
+ str: A string of hexadecimal values separated by spaces.
186
+ '''
187
+ return ' '.join(hex(b) for b in bytes_arr)
188
+
189
+ def _transfer(self, telegram_type, node, obj, data,
190
+ read_buff_len: int = 100) -> bytes:
191
+ '''
192
+ Transfers data to and from a serial device.
193
+ This method constructs a telegram based on the provided telegram_type, node, object, and data,
194
+ sends it to the serial device, and reads the response. It also performs verbosity-based
195
+ logging, checks the response length, and validates the checksum and error status.
196
+ Args:
197
+ telegram_type (int): The type of telegram to construct.
198
+ node (int): The node identifier.
199
+ obj (int): The object identifier.
200
+ data (bytes): The data to be included in the telegram.
201
+ read_buff_len(int): Bytes read buffer length.
202
+ Returns:
203
+ bytes: The response received from the serial device.
204
+ Raises:
205
+ SystemExit: If the response received is shorter than expected.
206
+ '''
207
+ telegram = eaps2k._construct_telegram(telegram_type, node, obj, data)
208
+
209
+ if self._verbosity_lvl >= 3:
210
+ print(f'-- telegram:\t\t{eaps2k.bytes2hex(telegram)}')
211
+
212
+ self.ser_dev.write(telegram) # send telegram
213
+ ans = self.ser_dev.read(read_buff_len)
214
+
215
+ if self._verbosity_lvl >= 3:
216
+ print(f'-- answer:\t\t{eaps2k.bytes2hex(telegram)}')
217
+
218
+ min_len = 5 # 5 bytes is the minimum length of a valid answer
219
+ assert len(ans) >= min_len, \
220
+ f'Short answer {len(ans)} bytes received, expected at least {min_len} bytes)'
221
+
222
+ # check the answer
223
+ eaps2k._checksum_verify(ans)
224
+ eaps2k._check_error(ans)
225
+
226
+ return ans
227
+
228
+ def _read_obj(self, obj, obj_type: type = bytes):
229
+ allowed_obj_types = [bytes, str, float, int]
230
+ assert obj_type in allowed_obj_types, \
231
+ f'ERROR: Object type shall be one of {allowed_obj_types} ' \
232
+ f'but it is {obj_type if obj_type is not type else type(obj_type)}'
233
+
234
+ msg = self._transfer(self.PS_QUERY, 0, obj, '')[3:-2]
235
+ if obj_type is bytes:
236
+ return msg
237
+ elif obj_type is str:
238
+ return msg.decode('ascii')
239
+ elif obj_type is float:
240
+ return struct.unpack('>f', msg)[0]
241
+ elif obj_type is int:
242
+ return struct.unpack('>H', msg)[0]
243
+ else:
244
+ assert False, 'ERROR: Unknown!'
245
+
246
+ def _write_obj(self, obj, data, obj_type: type = bytes, mask=None):
247
+ allowed_obj_types = [bytes, int]
248
+ assert obj_type in allowed_obj_types, \
249
+ f'ERROR: Object type shall be one of {allowed_obj_types} ' \
250
+ f'but it is {obj_type if obj_type is not type else type(obj_type)}'
251
+
252
+ if obj_type is bytes:
253
+ assert mask is not None, f'ERROR: The mask argument value {mask} is not allowed!'
254
+ ans = self._transfer(self.PS_SEND, 0, obj, [mask, data])
255
+ return ans[3:-2]
256
+ elif obj_type is int:
257
+ ans = self._transfer(self.PS_SEND, 0, obj, [int(data) >> 8, int(data) & 0xff])
258
+ return (ans[3] << 8) + ans[4]
259
+ else:
260
+ assert False, 'ERROR: Unknown!'
261
+
262
+ def get_type(self):
263
+ '''
264
+ object 0
265
+ see: object_list_ps2000b_de_en.pdf
266
+ '''
267
+ return self._read_obj(0, str)
268
+
269
+ def get_serial(self):
270
+ '''
271
+ object 1
272
+ see: object_list_ps2000b_de_en.pdf
273
+ '''
274
+ return self._read_obj(1, str)
275
+
276
+ def get_nominal_voltage(self):
277
+ '''
278
+ object 2
279
+ see: object_list_ps2000b_de_en.pdf
280
+ '''
281
+ return float(self._read_obj(2, float))
282
+
283
+ def get_nominal_current(self):
284
+ '''
285
+ object 3
286
+ see: object_list_ps2000b_de_en.pdf
287
+ '''
288
+ return float(self._read_obj(3, float))
289
+
290
+ def get_nominal_power(self):
291
+ '''
292
+ object 4
293
+ see: object_list_ps2000b_de_en.pdf
294
+ '''
295
+ return self._read_obj(4, float)
296
+
297
+ def get_article(self):
298
+ '''
299
+ object 6
300
+ see: object_list_ps2000b_de_en.pdf
301
+ '''
302
+ return self._read_obj(6, str)
303
+
304
+ def get_manufacturer(self):
305
+ '''
306
+ object 8
307
+ see: object_list_ps2000b_de_en.pdf
308
+ '''
309
+ return self._read_obj(8, str)
310
+
311
+ def get_version(self):
312
+ '''
313
+ object 9
314
+ see: object_list_ps2000b_de_en.pdf
315
+ '''
316
+ return self._read_obj(9, str)
317
+
318
+ def get_device_class(self):
319
+ '''
320
+ object 19
321
+ see: object_list_ps2000b_de_en.pdf
322
+
323
+ avialable classes:
324
+ 0x0010 = PS 2000 B Single, 0x0018 = PS 2000 B Triple
325
+ '''
326
+ ans = int(self._read_obj(19, int))
327
+ dev_class_str = 'unknown'
328
+ if ans == 0x0010:
329
+ dev_class_str = 'PS 2000 B Single'
330
+ elif ans == 0x0018:
331
+ dev_class_str = 'PS 2000 B Triple'
332
+ return (ans, dev_class_str)
333
+
334
+ def get_ovp(self):
335
+ '''
336
+ object 38
337
+ see: object_list_ps2000b_de_en.pdf
338
+ '''
339
+ return self.percent2real(self._u_nom, float(self._read_obj(38, int)))
340
+
341
+ def set_ovp(self, u):
342
+ '''
343
+ object 38
344
+ see: object_list_ps2000b_de_en.pdf
345
+ '''
346
+ if self._verbosity_lvl >= 1:
347
+ print(f'Set Over-Voltage-Protection: {u}')
348
+ return self._write_obj(38, int(round(self.real2percent(self._u_nom, u))), int)
349
+
350
+ def get_ocp(self):
351
+ '''
352
+ object 39
353
+ see: object_list_ps2000b_de_en.pdf
354
+ '''
355
+ return self.percent2real(self._i_nom, float(self._read_obj(39, int)))
356
+
357
+ def set_ocp(self, i):
358
+ '''
359
+ object 39
360
+ see: object_list_ps2000b_de_en.pdf
361
+ '''
362
+ if self._verbosity_lvl >= 1:
363
+ print(f'Set Over-Current-Protection: {i}')
364
+ return self._write_obj(39, int(round(self.real2percent(self._i_nom, i))), int)
365
+
366
+ def get_voltage_setpoint(self):
367
+ '''
368
+ object 50
369
+ see: object_list_ps2000b_de_en.pdf
370
+ '''
371
+ return self.percent2real(self._u_nom, float(self._read_obj(50, int)))
372
+
373
+ def set_voltage(self, u):
374
+ '''
375
+ object 50
376
+ see: object_list_ps2000b_de_en.pdf
377
+ '''
378
+ if self._verbosity_lvl >= 1:
379
+ print(f'Set Voltage: {u}')
380
+ return self._write_obj(50, int(round(self.real2percent(self._u_nom, u))), int)
381
+
382
+ def get_current_setpoint(self):
383
+ '''
384
+ object 51
385
+ see: object_list_ps2000b_de_en.pdf
386
+ '''
387
+ return self.percent2real(self._i_nom, float(self._read_obj(51, int)))
388
+
389
+ def set_current(self, i):
390
+ '''
391
+ object 51
392
+ see: object_list_ps2000b_de_en.pdf
393
+ '''
394
+ if self._verbosity_lvl >= 1:
395
+ print(f'Set Current: {i}')
396
+ return self._write_obj(51, int(round(self.real2percent(self._i_nom, i))), int)
397
+
398
+ def get_control(self):
399
+ '''
400
+ object 54
401
+ see: object_list_ps2000b_de_en.pdf
402
+ '''
403
+ ans = bytes(self._read_obj(54))
404
+ control = {
405
+ 'output_on': True if ans[1] & 0x01 else False,
406
+ 'remote': True if ans[0] & 0x01 else False
407
+ }
408
+ return control
409
+
410
+ def _set_control(self, mask, data):
411
+ '''
412
+ object 54
413
+ see: object_list_ps2000b_de_en.pdf
414
+ '''
415
+ ans = bytes(self._write_obj(54, data, bytes, mask))
416
+ # return True if command was acknowledged ("error 0")
417
+ return ans[0] == 0xff and ans[1] == 0x00
418
+
419
+ def ack_alarm(self):
420
+ if self._verbosity_lvl >= 2:
421
+ print('ACK Alarm')
422
+ return self._set_control(0x0a, 0x0a)
423
+
424
+ def get_remote(self):
425
+ return self.get_control()['remote']
426
+
427
+ def set_remote(self, remote=True):
428
+ if remote:
429
+ return self._set_control(0x10, 0x10)
430
+ else:
431
+ return self._set_control(0x10, 0x00)
432
+
433
+ def set_local(self, local=True):
434
+ return self.set_remote(not local)
435
+
436
+ def get_output_state(self):
437
+ return self.get_control()['output_on']
438
+
439
+ def set_output_state(self, on=True):
440
+ data = 0x01 if on else 0x00
441
+ if self._verbosity_lvl >= 2:
442
+ print(f"Set Output: {'on' if on else 'off'}")
443
+ return self._set_control(0x01, data)
444
+
445
+ def percent2real(self, nominal_value: float, percent_value: float) -> float:
446
+ '''
447
+ Convert percent value to real one by utilizing following equation:
448
+ (nominal_value * percent_value) / translation_factor
449
+ see: PS2000B_TFT_Programming_English.pdf, page 4
450
+ '''
451
+ return (nominal_value * percent_value) / self._translation_factor
452
+
453
+ def real2percent(self, nominal_value: float, value: float) -> float:
454
+ '''
455
+ Convert real value to percent one by utilizing following equation:
456
+ (translation_factor * value) / nominal_value
457
+ see: PS2000B_TFT_Programming_English.pdf, page 4
458
+ '''
459
+ return (self._translation_factor * value) / nominal_value
460
+
461
+ def get_actual(self):
462
+ '''
463
+ object 71
464
+ see: object_list_ps2000b_de_en.pdf
465
+ '''
466
+ ans = bytes(self._read_obj(71))
467
+ state = {
468
+ 'remote': True if ans[0] & 0x03 else False,
469
+ 'on': True if ans[1] & 0x01 else False,
470
+ 'CC': True if ans[1] & 0x06 else False,
471
+ 'CV': False if ans[1] & 0x06 else True, # not CC
472
+ 'tracking': True if ans[1] & 0x08 else False,
473
+ 'OVP': True if ans[1] & 0x10 else False,
474
+ 'OCP': True if ans[1] & 0x20 else False,
475
+ 'OPP': True if ans[1] & 0x40 else False,
476
+ 'OTP': True if ans[1] & 0x80 else False,
477
+ 'V': self.percent2real(self._u_nom, float((int(ans[2]) << 8) + int(ans[3]))),
478
+ 'I': self.percent2real(self._i_nom, float((int(ans[4]) << 8) + int(ans[5]))),
479
+ }
480
+ return state
481
+
482
+ def get_setpoints(self):
483
+ '''
484
+ object 72
485
+ see: object_list_ps2000b_de_en.pdf
486
+ '''
487
+ ans = bytes(self._read_obj(72))
488
+ state = {
489
+ 'remote': True if ans[0] & 0x03 else False,
490
+ 'on': True if ans[1] & 0x01 else False,
491
+ 'CC': True if ans[1] & 0x06 else False,
492
+ 'OVP': True if ans[1] & 0x10 else False,
493
+ 'OCP': True if ans[1] & 0x20 else False,
494
+ 'OPP': True if ans[1] & 0x40 else False,
495
+ 'OTP': True if ans[1] & 0x80 else False,
496
+ 'V': self.percent2real(self._u_nom, float((int(ans[2]) << 8) + int(ans[3]))),
497
+ 'I': self.percent2real(self._i_nom, float((int(ans[4]) << 8) + int(ans[5]))),
498
+ }
499
+ return state
500
+
501
+ @staticmethod
502
+ def get_config_template() -> dict:
503
+ '''
504
+ Returns a dictionary with the configuration template for the system.
505
+ The dictionary contains the following keys:
506
+ - 'ACK': Acknowledge alarms, initialized to False (bool)
507
+ - 'OVP': Over Voltage Protection, initialized to 0.0 (float)
508
+ - 'OCP': Over Current Protection, initialized to 0.0 (float)
509
+ - 'Iset': Current setpoint, initialized to 0.0 (float)
510
+ - 'Vset': Voltage setpoint, initialized to 0.0 (float)
511
+ Returns:
512
+ dict: A dictionary with the configuration template.
513
+ '''
514
+ return {'ACK': bool(False), 'OVP': int(0), 'OCP': int(0),
515
+ 'Iset': float(0.0), 'Vset': float(0.0)}
516
+
517
+ def configure(self, cfg: dict):
518
+ '''
519
+ Configures the device with the provided settings.
520
+ Parameters:
521
+ cfg (dict): A dictionary containing configuration settings.
522
+ The dictionary should have the following keys:
523
+ - 'ACK' (bool): Acknowledge alarms.
524
+ - 'OCP' (float): Over-Current-Protection threshold.
525
+ - 'OVP' (float): Over-Voltage-Protection threshold.
526
+ - 'Vset' (float): Voltage setting.
527
+ - 'Iset' (float): Current setting.
528
+ The method sets the corresponding thresholds and settings for the
529
+ device based on the provided configuration.
530
+ '''
531
+ if cfg['ACK'] is True:
532
+ self.ack_alarm()
533
+
534
+ if isinstance(cfg['OCP'], float):
535
+ self.set_ocp(cfg['OCP'])
536
+
537
+ if isinstance(cfg['OVP'], float):
538
+ self.set_ovp(cfg['OVP'])
539
+
540
+ if isinstance(cfg['Vset'], float):
541
+ self.set_voltage(cfg['Vset'])
542
+
543
+ if isinstance(cfg['Iset'], float):
544
+ self.set_current(cfg['Iset'])
545
+
546
+ def print_info(self):
547
+ '''
548
+ Get info from device and print it.
549
+ '''
550
+ dev_class_nr, dev_class_str = self.get_device_class()
551
+ dev_state = self.get_actual()
552
+ print(
553
+ f'type {self.get_type()}\n'
554
+ f'serial {self.get_serial()}\n'
555
+ f'article {self.get_article()}\n'
556
+ f'manuf {self.get_manufacturer()}\n'
557
+ f'version {self.get_version()}\n'
558
+ f'nom. voltage {self.get_nominal_voltage()}\n'
559
+ f'nom. current {self.get_nominal_current()}\n'
560
+ f'nom. power {self.get_nominal_power()}\n'
561
+ f'class {hex(dev_class_nr)} ({dev_class_str})\n'
562
+ f'OVP {self.get_ovp()}\n'
563
+ f'OCP {self.get_ocp()}\n'
564
+ f'control {self.get_control()}\n'
565
+ f'state {dev_state}'
566
+ )
567
+
568
+
569
+ def main():
570
+ parser = argparse.ArgumentParser(description=eaps2k.description())
571
+ parser.add_argument('--version', action='version',
572
+ version=f'%(prog)s {eaps2k.pkg_version()}')
573
+ parser.add_argument(
574
+ '-p', '--port', type=str, help='serial port to use', required=True)
575
+
576
+ default_voltage = None
577
+ default_current = None
578
+ parser.add_argument('-V', '--voltage', type=float, default=default_voltage,
579
+ required=False,
580
+ help=f'Voltage to be set. Nothing will be changed if None.'
581
+ f'(default: {default_voltage})')
582
+ parser.add_argument('--ovp', type=float, default=default_voltage,
583
+ required=False,
584
+ help=f'OVP - Over-Voltage-Protection. '
585
+ f'Nothing will be changed if None.'
586
+ f'(default: {default_voltage})')
587
+ parser.add_argument('-I', '--current', type=float, default=default_current,
588
+ required=False,
589
+ help=f'Current to be set. Nothing will be changed if None.'
590
+ f'(default: {default_current})')
591
+ parser.add_argument('--ocp', type=float, default=default_current,
592
+ required=False,
593
+ help=f'OCP - Over-Current-Protection. '
594
+ f'Nothing will be changed if None.'
595
+ f'(default: {default_current})')
596
+
597
+ group_verb = parser.add_mutually_exclusive_group(required=False)
598
+ group_verb.add_argument('-v', dest='verbose', action='count', default=0,
599
+ help='increase verbosity level')
600
+
601
+ group = parser.add_mutually_exclusive_group(required=True)
602
+ group.add_argument('--on', help='turn on', action='store_true')
603
+ group.add_argument('--off', help='turn off', action='store_true')
604
+ group.add_argument('--toggle', help='toggle output on/off', action='store_true')
605
+ group.add_argument('--info', help='print info and exit', action='store_true')
606
+ group.add_argument('--ack', help='Acknowledge alarms', action='store_true')
607
+ args = parser.parse_args()
608
+
609
+ cfg = eaps2k.get_config_template()
610
+ cfg['ACK'] = args.ack
611
+ cfg['OVP'] = args.ovp
612
+ cfg['OCP'] = args.ocp
613
+ cfg['Iset'] = args.current
614
+ cfg['Vset'] = args.voltage
615
+
616
+ with eaps2k(args.port, verbosity_level=args.verbose) as ps:
617
+ ps.configure(cfg) # set configuration do nothing if value(s) is/are None
618
+ if args.on:
619
+ ps.set_output_state(True)
620
+ elif args.off:
621
+ ps.set_output_state(False)
622
+ elif args.toggle:
623
+ state = ps.get_output_state()
624
+ ps.set_output_state(not state)
625
+ elif args.info:
626
+ ps.print_info()
627
+
628
+
629
+ if __name__ == '__main__':
630
+ sys.exit(main())