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