PyAR488 1.0__tar.gz → 1.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyAR488
3
- Version: 1.0
3
+ Version: 1.2
4
4
  Summary: module to interface AR488 boards
5
5
  Home-page: https://github.com/Minu-IU3IRR/PyAR488
6
6
  Author: Manuel Minutello
@@ -0,0 +1,405 @@
1
+
2
+ class AR488:
3
+ """Class to easily comunicate with an AR488 USB-GPIB adapter.
4
+ For details see: https://github.com/Twilight-Logic/AR488
5
+ -> implemented by Minu
6
+ """
7
+
8
+ import serial
9
+ import time
10
+
11
+ _valid_mode = ['device', 'controller']
12
+ _valid_eor = ['\r\n', '\r', '\n', None, '\n\r', '\x03', '\r\n\x03', 'EOI']
13
+ _valid_eos = ['\r\n', '\r', '\n', None]
14
+
15
+ def __init__(self, port:str, baud:int=115200, timeout:int=1, debug:bool=False):
16
+ try:
17
+ self._ser = self.serial.Serial(
18
+ port=port, baudrate=baud, timeout=timeout)
19
+ self.time.sleep(2) # await for serial interface open
20
+ except Exception as e:
21
+ raise Exception("error opening serial port {}".format(e))
22
+
23
+ self._debug_messages = False # do not show initial config transactions
24
+
25
+ self._address = 1 #
26
+ self._mode = 'controller' #
27
+ self._eoi = True #
28
+ self._eos = '\r\n' #
29
+ self._eot = True #
30
+ self._eot_char = '' #
31
+ self._eor = '\r\n' #
32
+ self._idn = '0\r\n' #
33
+
34
+ self.update_config() # needed to check eos
35
+
36
+ self._debug_messages = debug
37
+
38
+ def update_config(self):
39
+ self._address = int(self.address())
40
+ self._mode = self.mode()
41
+ self._eoi = self.eoi()
42
+ self._eos = self.eos()
43
+ self._eot = self.eot()
44
+ self._eot_char = self.eot_char()
45
+ self._eor = self.eor()
46
+ self._idn = self.idn()
47
+
48
+ def __del__(self):
49
+ self._ser.close()
50
+
51
+ def close(self):
52
+ self.__del__()
53
+
54
+ def __enter__(self):
55
+ return self
56
+
57
+ def __exit__(self, exc_type, exc_value, exc_tb):
58
+ self._ser.close()
59
+
60
+ # bus commands
61
+ def bus_write(self, message:str):
62
+ """write a message on bus"""
63
+ self._ser.write("{}\n\r".format(message).encode("ASCII"))
64
+ if self._debug_messages: # debug
65
+ print(f'AR488 -> write :{message}')
66
+
67
+ def bus_read(self, decode:bool=True):
68
+ """Read from GPIB bus, decode if specified"""
69
+ val = self._ser.readline()
70
+ if val == b'Unrecognized command\r\n':
71
+ raise Exception('Unrecognized command')
72
+ if decode:
73
+ val = val.decode('utf-8')
74
+
75
+ if self._debug_messages: # debug
76
+ print(f'AR488 -> read :{val}')
77
+ return val
78
+
79
+ def query(self, message:str, payload:bool=False, decode:bool=True):
80
+ """Write message to GPIB bus and read results, if a payload is expected send '++read' too,
81
+ decode by default un UTF-8"""
82
+ self.bus_write(message)
83
+ if payload:
84
+ return self.read(decode=decode)
85
+ return self.bus_read(decode=decode)
86
+
87
+ # Prologix commands
88
+ def address(self, new_address: int = None, force: bool = False):
89
+ """This is used to set or query the GPIB address. At present, only primary addresses are supported. In
90
+ controller mode, the address refers to the GPIB address of the instrument that the operator
91
+ desires to communicate with. The address of the controller is 0. In device mode, the address
92
+ represents the address of the interface which is now acting as a device.
93
+ ose force = True optional parameter to force a new address set"""
94
+ if new_address is None:
95
+ return self.query("++addr")
96
+ else:
97
+ if self._address != new_address or force:
98
+ if 1 <= new_address <= 30:
99
+ self.bus_write(f'++addr {new_address}')
100
+ else:
101
+ raise Exception(f'invalid address {new_address}')
102
+
103
+ # check for correct execution
104
+ self._address = int(self.address())
105
+ if self._address != new_address:
106
+ raise Exception('Failed to set interface address')
107
+
108
+ # todo : ++auto
109
+
110
+ def clear(self):
111
+ """This command sends a Selected Device Clear (SDC) to the currently addressed instrument. Details
112
+ of how the instrument should respond may be found in the instrument manual"""
113
+ self.bus_write('++clr')
114
+
115
+ def eoi(self, new_eoi: bool = None):
116
+ """This command enables or disables the assertion of the EOI signal. When a data message is sent in
117
+ binary format, the CR/LF terminators cannot be differentiated from the binary data bytes. In this
118
+ circumstance, the EOI signal can be used as a message terminator. When ATN is not asserted and
119
+ EOI is enabled, the EOI signal will be briefly asserted to indicate the last character sent in a multibyte
120
+ sequence. Some instruments require their command strings to be terminated with an EOI
121
+ signal in order to properly detect the command"""
122
+ if new_eoi is None:
123
+ return bool(self.query('++eoi'))
124
+ else:
125
+ self.bus_write(f'++eoi {"1" if new_eoi else "0"}')
126
+
127
+ # check
128
+ self._eoi = self.eoi()
129
+ if self._eoi != new_eoi:
130
+ raise Exception('unable to set new eoi mode')
131
+
132
+ def eos(self, new_eos: str = None):
133
+ """Specifies the GPIB termination character. When data from the host (e.g. a command sequence) is
134
+ received over USB, all non-escaped LF, CR or Esc characters are removed and replaced by the GPIB
135
+ termination character, which is appended to the data sent to the instrument. This command does
136
+ not affect data being received from the instrument"""
137
+ if new_eos is None:
138
+ return self._valid_eos[int(self.query('++eos'))]
139
+ else:
140
+ if new_eos in self._valid_eos:
141
+ self.bus_write(f'++eos {self._valid_eos.index(new_eos)}')
142
+ else:
143
+ raise Exception('invalid eor char sequence')
144
+
145
+ # check
146
+ self._eos = self.eos()
147
+ if self._eos != new_eos:
148
+ raise Exception('unable to set new eos char sequence')
149
+
150
+ def eot(self, new_eot: bool = None):
151
+ """This command enables or disables the appending of a user specified character to the USB output
152
+ from the interface to the host whenever EOI is detected while reading data from the GPIB port.
153
+ The character to send is specified using the prologix_eot_char(str) witch sends ++eot_char command"""
154
+ if new_eot is None:
155
+ response = self.query('++eot_enable')
156
+ if response == '0':
157
+ return False
158
+ return True
159
+ else:
160
+ self.bus_write(f'++eot_enable {"1" if new_eot else "0"}')
161
+
162
+ # check
163
+ self._eot = self.eot()
164
+ if self._eot != new_eot:
165
+ raise Exception('unable to set new eot mode')
166
+
167
+ def eot_char(self, new_eot_char: str = None):
168
+ """This command specifies the character to be appended to the USB output from the interface to the
169
+ host whenever an EOI signal is detected while reading data from the GPIB bus. The character is a
170
+ decimal ASCII character"""
171
+ if new_eot_char is None:
172
+ return chr(int(self.query('++eot_char'))) # store as char
173
+ else:
174
+ new_eot_char = ord(new_eot_char)
175
+ if 0 <= new_eot_char <= 256:
176
+ self.bus_write(f'++eot_char {new_eot_char}') # send as char
177
+ else:
178
+ raise Exception('invalid eot_char')
179
+
180
+ # check
181
+ self._eot_char = self.eot_char()
182
+ if self._eot_char == new_eot_char:
183
+ raise Exception('unable to set new eos char sequence')
184
+
185
+ # todo : ++help, ++ifc, ++llo, ++loc, ++lon,
186
+
187
+ def mode(self, new_mode=None):
188
+ """This command configures the AR488 to serve as a controller or a device.
189
+ In controller mode the AR488 acts as the Controller-in-Charge (CIC) on the GPIB bus, receiving
190
+ commands terminated with CRLF over USB and sending them to the currently addressed
191
+ instrument via the GPIB bus. The controller then passes the received data back over USB to the
192
+ host.
193
+ In device mode, the AR488 can act as another device on the GPIB bus. In this mode, the AR488 can
194
+ act as a GPIB talker or listener and expects to receive commands from another controller (CIC). All
195
+ data received by the AR488 is passed to the host via USB without buffering. All data from the host
196
+ via USB is buffered until the AR488 is addressed by the controller to talk.
197
+ At this point the AR488 sends the buffered data to the controller. Since the memory on the controller is
198
+ limited, the AR488 can buffer only 120 characters at a time.
199
+ When sending data followed by a command, the buffer must first be read by the controller before
200
+ a subsequent command can be accepted, otherwise the command will be treated as characters to
201
+ be appended to the existing data in the buffer."""
202
+ if new_mode is None:
203
+ return self._valid_mode[int(self.query('++mode'))]
204
+ else:
205
+ if type(new_mode) == str and new_mode not in self._valid_mode:
206
+ raise Exception('invalid interface operation mode')
207
+ else:
208
+ new_mode = self._valid_mode.index(new_mode) # get mode index
209
+
210
+ if type(new_mode) == int and new_mode in range(len(self._valid_mode)):
211
+ self.bus_write(f'++mode {self._valid_mode[new_mode]}')
212
+ else:
213
+ raise Exception('invalid mode type')
214
+
215
+ # check
216
+ self._mode = self.mode() # string
217
+ if self._mode != new_mode:
218
+ raise Exception('unable to set correct operation mode')
219
+
220
+ def read(self, terminator: str = None, decode:bool=True, until_eoi:bool=False):
221
+ """This command can be used to read data from the currently addressed instrument. Data is read
222
+ until:
223
+  the EOI signal is detected
224
+  a specified character is read
225
+  timeout expires
226
+ Timeout is set using the read_tmo_ms command (still to be implemented) and is the maximum permitted
227
+ delay for a single character to be read. It is not related to the time taken to read all the data.
228
+ For details see the description of the read_tmo_ms command."""
229
+ if until_eoi:
230
+ if not self._eoi:
231
+ raise Exception('EOI function not enabled')
232
+ return self.query(f'++read eoi', decode=decode)
233
+ else:
234
+ return self.query(f'++read{" " + terminator if terminator is not None else ""}',
235
+ decode=(decode if decode is not None else None))
236
+
237
+ # todo : ++read_to_ms
238
+
239
+ def prologix_reset(self):
240
+ """Perform a reset of the controller."""
241
+ self.bus_write('++rst')
242
+
243
+ # todo : ++savecfg
244
+
245
+ def spoll(self, targets=None):
246
+ """Performs a serial poll. If no parameters are specified, the command will perform a serial poll of the
247
+ currently addressed instrument. If a GPIB address is specified, then a serial poll of the instrument
248
+ at the specified address is performed. The command returns a single 8-bit decimal number
249
+ representing the status byte of the instrument.
250
+ The command can also be used to serial poll multiple instruments. Up to 15 addresses can be
251
+ specified. If the all parameter is specified, then a serial poll of all 30 primary instrument addresses
252
+ is performed.
253
+ When polling multiple addresses, the prologix_spoll() command will return the address and status byte of
254
+ the first instrument it encounters that has the RQS bit set in its status byte, indicating that it has
255
+ requested service. The format of the response is SRQ:addr,status, for example: SRQ:3,88 where 3
256
+ is the GPIB address of the instrument and 88 is the status byte. The response provides a means to
257
+ poll a number of instruments and to identify which instrument raised the service request, all in
258
+ one command. If SRQ was not asserted then no response will be returned.
259
+ When ++srqauto is set to 1 (for details see the ++srqauto custom command), the interface will
260
+ automatically conduct a serial poll of all devices on the GPIB bus whenever it detects that SRQ has
261
+ been asserted and the details of the instrument that raised the request are automatically returned
262
+ to the format above."""
263
+ target_string = ''
264
+ if targets is not None:
265
+ if type(targets) == list or type(targets) == tuple:
266
+ if len(targets) < 15:
267
+ target_string += ' all '
268
+ for i in targets:
269
+ target_string += f' {i}'
270
+ else:
271
+ raise Exception('too many spoll addresses, max is 15')
272
+ elif type(targets) == int and 0 < targets < 30:
273
+ target_string += f' {targets}'
274
+
275
+ return self.query('++spoll' + target_string)
276
+
277
+ def read_srq(self):
278
+ """This command returns the present status of the SRQ signal line. It returns False if SRQ is not asserted
279
+ and True if SRQ is asserted"""
280
+ return bool(self.query('++srq'))
281
+
282
+ # todo : ++status
283
+
284
+ def send_trigger(self, target):
285
+ """Sends a Group Execute Trigger to selected devices. Up to 15 addresses may be specified and must
286
+ be separated by spaces. If no address is specified, then the command is sent to the currently
287
+ addressed instrument. The instrument needs to be set to single trigger mode and remotely
288
+ controlled by the GPIB controller. Using ++trg, the instrument can be manually triggered and the
289
+ result read with prologix_read()"""
290
+ if type(target) == int and 1 <= target <= 30:
291
+ self.bus_write(f'++trg {target}')
292
+ elif type(target) == list or type(target) == tuple:
293
+ if len(target) > 15:
294
+ raise Exception(
295
+ 'too many targets, group trigger support max 15 devices ')
296
+ target_string = ''
297
+ for i in target:
298
+ if i == int and 1 <= i == 30:
299
+ target_string += f' {i}'
300
+ else:
301
+ raise Exception('invalid type in trigger targets')
302
+ self.bus_write('trg' + target_string)
303
+ else:
304
+ raise Exception('invalid type of trigger target')
305
+
306
+ def ver(self):
307
+ """Display the controller firmware version. If the version string has been changed with ++setvstr
308
+ ( still to be implemented ), then prologix_ver() will display the new version string. Issuing the command
309
+ with the parameter ‘real’ will always display the original AR488 version string"""
310
+ return self.bus_write('++ver')
311
+
312
+ # custom commands
313
+ # todo : ++allspoll,
314
+
315
+ def prologix_device_clear(self):
316
+ """Send Device Clear (DCL) to all devices on the GPIB bus."""
317
+ self.bus_write('++dcl')
318
+
319
+ # todo : ++default
320
+
321
+ def eor(self, new_eor: str = None):
322
+ """End of receive. While prologix_eos(str) (end of send) selects the terminator to add to commands and data
323
+ being sent to the instrument, the prologix_eor(str) command selects the expected termination sequence
324
+ when receiving data from the instrument. The default termination sequence is CR + LF.
325
+ If the command is specified with one of the above numeric options, then the corresponding termination
326
+ sequence will be used to detect the end of the data being transmitted from the instrument. If the command
327
+ is specified without a parameter, then it will return the current setting. If option "EOI" is selected,
328
+ then prologix_read() eoi is implied for all prologix_read() instructions as well as any data being returned by
329
+ the instrument in response to direct instrument commands. An EOI is expected to be signalled by the instrument
330
+ with the last character of any transmission sent. All characters sent over the GPIB bus are passed to the
331
+ serial port for onward transmission to the host computer"""
332
+ if new_eor is None:
333
+ return self._valid_eor[int(self.query('++eor'))]
334
+ else:
335
+ if new_eor in self._valid_eor:
336
+ self.bus_write(f'++eor {self._valid_eor.index(new_eor)}')
337
+ else:
338
+ raise Exception('invalid eor char sequence')
339
+
340
+ # check
341
+ current_eor = self.eor()
342
+ if current_eor != new_eor:
343
+ raise Exception('unable to set new eor char sequence')
344
+
345
+ # todo : ++id, ++id fwver, ++id serial, ++id verstr
346
+
347
+ def idn(self, new_idn=None):
348
+ """This command is used to enable the facility for the interface to respond to a SCPI *idn? Command.
349
+ Some older instruments do no respond to a SCPI ID request but this feature will allow the interface
350
+ to respond on behalf of the instrument using parameters set with the ++id command. When set to
351
+ zero, response to the SCPI *idn? command is disabled and the request is passed to the instrument.
352
+ When set to 1, the interface responds with the name set using the ++idn name command. When
353
+ set to 2, the instrument also appends the serial number using the format name-99999999"""
354
+ if new_idn is None:
355
+ return self.query('++idn')
356
+ else:
357
+ if 0 <= new_idn <= 2:
358
+ self.bus_write(f'++idn {new_idn}')
359
+ else:
360
+ raise Exception('invalid idn mode')
361
+
362
+ self._idn = self.idn()
363
+ if self._idn != new_idn:
364
+ raise Exception('unable to set new idn mode')
365
+
366
+ # todo : ++macro, ++ppoll, ++prom, ++ren, ++repeat, ++setvstr, ++srqauto, ton, ++verbose
367
+
368
+ # notes:
369
+ # '\r', '\n', and '+' are control characters that must be escaped in binary data
370
+ #
371
+ # Prologix commands:
372
+ # ++addr [1-29]
373
+ # ++auto [0 | 1 | 2 | 3]
374
+ # ++clr
375
+ # ++eoi [0 | 1]
376
+ # ++eos [0 | 2 | 3 | 4]
377
+ # ++eot_enable [0 | 1]
378
+ # ++eot_char [<char>]
379
+ # ++help (unsupported)
380
+ # ++ifc
381
+ # ++llo [all]
382
+ # ++loc [all]
383
+ # ++lon (unsupported)
384
+ # ++mode [0 | 1]
385
+ # ++read [eoi | <char>]
386
+ # ++read_tmo_ms <time>
387
+ # ++rst
388
+ # ++savecfg
389
+ # ++spoll [<PAD> | all | <PAD1> <PAD2> <PAD3> ...]
390
+ # ++srq
391
+ # ++status [<byte>]
392
+ # ++trg [PAD1 ... PAD15]
393
+ # ++ver [real]
394
+ #
395
+ # Custom AR488 commands:
396
+ # ++allspoll
397
+ # ++dl
398
+ # ++default
399
+ # ++macro [1-9]
400
+ # ++ppoll
401
+ # ++setvstr [string]
402
+ # ++srqauto [0 | 1]
403
+ # ++repeat count delay cmdstring
404
+ # ++tmbus [value]
405
+ # ++verbose
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyAR488
3
- Version: 1.0
3
+ Version: 1.2
4
4
  Summary: module to interface AR488 boards
5
5
  Home-page: https://github.com/Minu-IU3IRR/PyAR488
6
6
  Author: Manuel Minutello
@@ -1,6 +1,8 @@
1
1
  LICENSE
2
2
  README.md
3
3
  setup.py
4
+ PyAR488/PyAR488.py
5
+ PyAR488/__init__.py
4
6
  PyAR488.egg-info/PKG-INFO
5
7
  PyAR488.egg-info/SOURCES.txt
6
8
  PyAR488.egg-info/dependency_links.txt
@@ -0,0 +1 @@
1
+ PyAR488
@@ -13,7 +13,7 @@ setup(
13
13
  version=local_build_version,
14
14
  packages=find_packages(exclude=('_INTERNAL_build.py',
15
15
  '_INTERNAL_version.json',
16
- '.gitignore'
16
+ '.gitignore',
17
17
  'workspace.code-workspace')),
18
18
  url="https://github.com/Minu-IU3IRR/PyAR488",
19
19
  bugtrack_url = 'https://github.com/Minu-IU3IRR/PyAR488/issues',
@@ -1 +0,0 @@
1
-
File without changes
File without changes
File without changes