Skip to content

Commit d10ac77

Browse files
committed
added HP34970A visa device
1 parent 7fa0901 commit d10ac77

2 files changed

Lines changed: 299 additions & 1 deletion

File tree

AUTHORS

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ Current and previous core designers:
99
Contributors (in alphabetical order):
1010

1111
* Camille Lavayssiere
12-
* Shannon McCullough
12+
* Shannon McCullough
13+
* Gregor Kendzierski

pyscada/visa/devices/HP34970A.py

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
from pyscada.visa.devices import GenericDevice
5+
from pyscada.models import VariableProperty
6+
from datetime import datetime
7+
from math import floor
8+
9+
import logging
10+
11+
logger = logging.getLogger(__name__)
12+
# -*- coding: utf-8 -*-
13+
"""Object based access to the HP34970A, Agilent 34970A/34972A , Keysight 34970A Data Acquisition / switching unit
14+
15+
16+
Example::
17+
18+
from HP34970A import HP34970A, Channel, DataTypes
19+
unit = HP34970A('ASRL/dev/ttyUSB0::INSTR')
20+
unit.connect()
21+
unit.set_channel(1,1,DataType.TEMP_RTD_85) # set channel 1 on bank 1 to Temperature 2-Wire RTD and a = 0.00385
22+
unit.set_channel(1,2,DataType.TEMP_FRTD_85) # set channel 2 on bank 1 to Temperature 4-Wire RTD and a = 0.00385
23+
unit.set_channel(1,3,DataType.TEMP_TC_T) # set channel 3 on bank 1 to Temperature Thermocouple Typ T
24+
timestamp, value = instr.get_value(1,1) # read measurement from channel 1 on bank 1
25+
if value:
26+
print('CH1: %1.3f'%value)
27+
instr.scan_all() # scan all configured channels and write to internal (object) memory
28+
timestamp, value = instr.get_mvalue(1,1) # read internal (object) memory of channel 1 on bank 1
29+
if value:
30+
print('CH1: %1.3f'%value)
31+
unit.disconnect()
32+
"""
33+
__author__ = "Martin Schröder"
34+
__copyright__ = "Copyright 2018, Technische Universität Berlin"
35+
__credits__ = []
36+
__license__ = "GPLv3"
37+
__version__ = "0.1.0"
38+
__maintainer__ = "Martin Schröder"
39+
__email__ = "m.schroeder@tu-berlin.de"
40+
__status__ = "Beta"
41+
__docformat__ = 'reStructuredText'
42+
43+
import pyvisa
44+
from datetime import datetime
45+
from math import floor
46+
from time import sleep
47+
48+
class DataType(object):
49+
CURRENT_AC = 'CURR:AC'
50+
CURRENT_DC = 'CURR:DC'
51+
CURRENT_DC_10mA = 'CURR:DC 10mA'
52+
CURRENT_DC_100mA = 'CURR:DC 100mA'
53+
CURRENT_DC_1A = 'CURR:DC 1A'
54+
CURRENT_AC_10mA = 'CURR:AC 10mA'
55+
CURRENT_AC_100mA = 'CURR:AC 100mA'
56+
CURRENT_AC_1A = 'CURR:AC 1A'
57+
FREQUENCY = 'FREQ'
58+
PERIOD = 'PER'
59+
RESISTANCE = 'RES' # 2-Wire Resistance
60+
RESISTANCE_100ohm = 'RES 100W' # 2-Wire Resistance
61+
RESISTANCE_1kohm = 'RES 1kW' # 2-Wire Resistance
62+
RESISTANCE_10kohm = 'RES 10kW' # 2-Wire Resistance
63+
RESISTANCE_100kohm = 'RES 100kW' # 2-Wire Resistance
64+
RESISTANCE_1Mohm = 'RES 1MW' # 2-Wire Resistance
65+
RESISTANCE_10Mohm = 'RES 10MW' # 2-Wire Resistance
66+
RESISTANCE_100Mohm = 'RES 100MW' # 2-Wire Resistance
67+
FRESISTANCE = 'FRES' # 4-Wire Resistance
68+
FRESISTANCE_100ohm = 'FRES 100W' # 4-Wire Resistance
69+
FRESISTANCE_1kohm = 'FRES 1kW' # 4-Wire Resistance
70+
FRESISTANCE_10kohm = 'FRES 10kW' # 4-Wire Resistance
71+
FRESISTANCE_100kohm = 'FRES 100kW' # 4-Wire Resistance
72+
FRESISTANCE_1Mohm = 'FRES 1MW' # 4-Wire Resistance
73+
FRESISTANCE_10Mohm = 'FRES 10MW' # 4-Wire Resistance
74+
FRESISTANCE_100Mohm = 'FRES 100MW' # 4-Wire Resistance
75+
TEMPERATURE = 'TEMP'
76+
TEMP_TC_B = 'TEMP TC,B'
77+
TEMP_TC_E = 'TEMP TC,E'
78+
TEMP_TC_J = 'TEMP TC,J'
79+
TEMP_TC_K = 'TEMP TC,K'
80+
TEMP_TC_N = 'TEMP TC,N'
81+
TEMP_TC_R = 'TEMP TC,R'
82+
TEMP_TC_S = 'TEMP TC,S'
83+
TEMP_TC_T = 'TEMP TC,T'
84+
TEMP_RTD_85 = 'TEMP RTD,85'
85+
TEMP_RTD_91 = 'TEMP RTD,85'
86+
TEMP_FRTD_85 = 'TEMP FRTD,85'
87+
TEMP_FRTD_91 = 'TEMP FRTD,85'
88+
TEMP_TERM_2252 = 'TEMP TERM,2252'
89+
TEMP_TERM_5000 = 'TEMP TERM,5000'
90+
TEMP_TERM_10000 = 'TEMP TERM,10000'
91+
TOTALIZE='TOT'
92+
VOLTAGE_AC='VOLT:AC'
93+
VOLTAGE_DC = 'VOLT:DC'
94+
VOLTAGE_AC_100mV = '100mV'
95+
VOLTAGE_AC_1V = 'VOLT:AC 1V'
96+
VOLTAGE_AC_10V = 'VOLT:AC 10V'
97+
VOLTAGE_AC_100V = 'VOLT:AC 100V'
98+
VOLTAGE_AC_300V = 'VOLT:AC 300V'
99+
VOLTAGE_DC_100mV = 'VOLT:DC 100mV'
100+
VOLTAGE_DC_1V = 'VOLT:DC 1V'
101+
VOLTAGE_DC_10V = 'VOLT:DC 10V'
102+
VOLTAGE_DC_100V = 'VOLT:DC 100V'
103+
VOLTAGE_DC_300V = 'VOLT:DC 300V'
104+
105+
106+
class Channel(object):
107+
data_type = DataType.VOLTAGE_DC
108+
value = None
109+
timestamp = None
110+
111+
def __init__(self,**kwargs):
112+
for key, value in kwargs.items():
113+
setattr(self, key, value)
114+
115+
BANKS = [1, 2, 3]
116+
CHANNELS = range(1,21)
117+
118+
class HP34970A(object):
119+
120+
banks = None
121+
_isconfigured = False
122+
def __init__(self):
123+
self.instr = None
124+
self._queries = dict()
125+
self._channels = dict()
126+
self.banks = []
127+
128+
def connect(self, instr):
129+
if self.instr is None and instr is None:
130+
return
131+
self.instr = instr
132+
self.instr.write("STATus:PRESet")
133+
self.instr.write('SYST:TIME %s'%(datetime.now().strftime("%H,%M,%S.000"))) # set unit time
134+
self.instr.write('SYST:DATE %s'%(datetime.now().strftime("%Y,%m,%d"))) # set unit date
135+
self.instr.write('FORM:READ:TIME:TYPE ABS')
136+
dispstring = "Booting."
137+
for i in range(len(dispstring)-13):
138+
self.instr.write('DISP:TEXT "%s"'%dispstring[i:i+13], encoding="ascii")
139+
sleep(0.1)
140+
self.instr.write('DISP:TEXT "Done"', encoding="ascii")
141+
sleep(1)
142+
self.instr.write('DISP:TEXT:CLE')
143+
for bank in BANKS:
144+
card_type = self.instr.query("SYSTem:CTYPe? %d00"%bank)
145+
if card_type in ["HEWLETT-PACKARD,34901A,0,2.3"]:
146+
self.banks.append(bank)
147+
148+
def set_channel(self, channel='101', data_type=DataType.VOLTAGE_DC, **kwargs):
149+
self._channels[channel] = Channel(data_type=data_type)
150+
self._isconfigured = False
151+
152+
def configure(self):
153+
self._queries = dict()
154+
for channel_nr, channel in self._channels.items():
155+
if channel.data_type not in self._queries:
156+
self._queries[channel.data_type] = '(@%s'%channel_nr
157+
else:
158+
self._queries[channel.data_type] += ',%s'%channel_nr
159+
160+
for data_type in self._queries.keys():
161+
self.instr.write('CONF:%s,%s)'%(data_type, self._queries[data_type]))
162+
self._isconfigured = True
163+
164+
def scan_all(self):
165+
if not self._isconfigured:
166+
return
167+
for data_type in self._queries.keys():
168+
self.scan(data_type)
169+
170+
def scan(self,data_type):
171+
for bank in self.banks:
172+
self.instr.write('ROUT:CHAN:DEL:AUTO ON,(@%d01:%d20)'%(bank,bank))
173+
#self.instr.write('CONF:%s,%s)'%(data_type, self._queries[data_type]))
174+
self.instr.write('FORM:READ:TIME:TYPE ABS')
175+
self.instr.write('FORM:READ:TIME ON')
176+
self.instr.write('FORM:READ:CHAN ON')
177+
self.instr.write('ROUT:SCAN %s)'%self._queries[data_type])
178+
self.instr.write('INIT')
179+
self.instr.write('FETC?')
180+
result = self.instr.read_ascii_values()
181+
self._parse_result(result)
182+
183+
def get_value(self,bank,channel_nr):
184+
channel = self._get_ch_str(bank, channel_nr)
185+
data_type = self._channels[channel].data_type
186+
#self.instr.write('CONF:%s,(@%s)'%(data_type,channel))
187+
self.instr.write('FORM:READ:TIME:TYPE ABS')
188+
self.instr.write('FORM:READ:TIME ON')
189+
self.instr.write('FORM:READ:CHAN ON')
190+
self.instr.write('ROUT:SCAN (@%s)'%channel)
191+
self.instr.write('INIT')
192+
self.instr.write('FETC?')
193+
result = self.instr.read_ascii_values()
194+
self._parse_result(result)
195+
return self.get_mvalue(bank, channel_nr)
196+
197+
def get_mvalue(self,bank,channel_nr):
198+
"""reads the value of channel from the object memory (self._channels)
199+
200+
:param bank: number of the bank [1:3]
201+
:param channel_nr: number of the channel [1:20]
202+
:return: [timestamp, value]
203+
"""
204+
channel = self._get_ch_str(bank,channel_nr)
205+
return self._channels[channel].timestamp, self._channels[channel].value
206+
207+
@staticmethod
208+
def _get_ch_str(bank,channel_nr):
209+
if channel_nr not in CHANNELS:
210+
raise ValueError('Channel out of range %r' % CHANNELS)
211+
if bank not in BANKS:
212+
raise ValueError('Bank out of range %r' % BANKS)
213+
214+
return '%d%02.2G' % (bank, channel_nr)
215+
216+
def _parse_result(self,result):
217+
for i in range(0,len(result),8):
218+
channel = str(int(result[i+7])) # Channel number
219+
self._channels[channel].value = result[i]
220+
self._channels[channel].timestamp = datetime(int(result[i+1]),
221+
int(result[i+2]),
222+
int(result[i+3]),
223+
int(result[i+4]),
224+
int(result[i+5]),
225+
int(floor(result[i+6])),
226+
int(round((result[i+6]%1)*1000))).timestamp()
227+
228+
from time import sleep
229+
class Handler(GenericDevice):
230+
"""
231+
HP34970A and other Devices with the same command set
232+
"""
233+
dmm = None
234+
def connect(self):
235+
super(Handler, self).connect()
236+
self.dmm = HP34970A()
237+
if self.inst is None:
238+
return
239+
try:
240+
logger.info(self.inst.query('*IDN?'))
241+
except:
242+
self.inst = None
243+
return
244+
self.dmm.connect(self.inst)
245+
for variable in self._variables.values():
246+
data_type, channel = variable.visavariable.device_property.upper().split(';')
247+
self.dmm.set_channel(channel=channel, data_type=data_type)
248+
self.dmm.configure()
249+
250+
def before_read(self):
251+
self.dmm.scan_all()
252+
253+
def read_data_and_time(self,variable_instance):
254+
"""
255+
read values from the device
256+
"""
257+
if self.inst is None:
258+
return None, None
259+
data_type, channel = variable_instance.visavariable.device_property.upper().split(';')
260+
return self.dmm._channels[channel].value, self.dmm._channels[channel].timestamp
261+
262+
def write_data(self,variable_id, value, task):
263+
"""
264+
write values to the device
265+
"""
266+
variable = self._variables[variable_id]
267+
if task.property_name != '':
268+
# write the freq property to VariableProperty use that for later read
269+
vp = VariableProperty.objects.update_or_create_property(variable=variable, name=task.property_name.upper(),
270+
value=value, value_class='FLOAT64')
271+
return True
272+
if variable.visavariable.variable_type == 0: # configuration
273+
# only write to configuration variables
274+
pass
275+
else:
276+
return False
277+
278+
def parse_value(self,result):
279+
"""
280+
takes a string in the HP3456A format and returns a float value or None if not parseable
281+
"""
282+
try:
283+
i = 0
284+
value = result[i]
285+
"""
286+
timestamp = datetime(int(result[i+1]),
287+
int(result[i+2]),
288+
int(result[i+3]),
289+
int(result[i+4]),
290+
int(result[i+5]),
291+
int(floor(result[i+6])),
292+
int(round((result[i+6]%1)*1000))).timestamp()
293+
"""
294+
return value
295+
except:
296+
return None
297+

0 commit comments

Comments
 (0)