From 9cadee3d5f20c4fac81ff377a6bbd747640e5d0f Mon Sep 17 00:00:00 2001 From: Jibril Mammeri Date: Mon, 8 Feb 2021 17:13:31 +0100 Subject: [PATCH 1/3] Define Lecroy commands --- bliss/controllers/lecroy.py | 660 ++++++++++++++++++++++++++++++++++++ 1 file changed, 660 insertions(+) create mode 100644 bliss/controllers/lecroy.py diff --git a/bliss/controllers/lecroy.py b/bliss/controllers/lecroy.py new file mode 100644 index 0000000000..0e40968436 --- /dev/null +++ b/bliss/controllers/lecroy.py @@ -0,0 +1,660 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# This file is part of the bliss project +# +# Copyright (c) 2015-2020 Beamline Control Unit, ESRF +# Distributed under the GNU LGPLv3. See LICENSE for more info. + +import time +import numpy as np +import io +import socket +import struct + +HEADER_FORMAT = ">BBBBL" +DATA_FLAG = 0x80 + + +class Lecroy620zi(object): + """ + Define the methods used with the Lecroy scope Wave Runner 620Zi. + """ + + def __init__(self, host, port=1861, timeout=5.0): + self._comm = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._comm.connect((host, port)) + self._comm.settimeout(timeout) + + def send(self, msg): + if not msg.endswith("\n"): + msg += "\n" + msg = msg.encode() + header = struct.pack(HEADER_FORMAT, 129, 1, 1, 0, len(msg)) + self._comm.sendall(b"".join([header, msg])) + + def recv(self, decode=True): + dtstr = [] + while True: + data = self._comm.recv(8) + head_data = struct.unpack("B3BI", data) # get response (header from device) + lnt = socket.ntohl(head_data[-1]) # data length to be captured + flg = head_data[0] + dtstr.append(self._comm.recv(lnt)) + if flg != DATA_FLAG: # data flag 0x80 + break + answr = b"".join(dtstr) + if decode: + answr = answr.decode() + if answr.endswith("\n"): + answr = answr[:-1] + return answr + + def rst(self): + """Initiate a device reset.""" + self.send("*RST") + + def rcl(self): + """Recalls one of five non-volatile panel setups.""" + self.send("*RCL") + + def clsw(self): + """Restarts the cummulative processing functions.""" + self.send("CLSW") + + def wait(self): + """Prevents new analysis until current is completd.""" + self.send("WAIT") + + def trg(self): + """Executes an ARM command.""" + self.send("*TRG") + + def get_trmd(self): + """ + Read the trigger mode. + """ + self.send("TRMD?") + return self.recv() + + def set_trmd(self, cmd): + """ + Specifies the trigger mode. + Example: set_trmd({"AUTO","NORM","STOP"}) + """ + self.send("TRMD " + cmd) + + def get_trlv(self): + """Adjusts the trigger level of the specified trigger source.""" + self.send("TRLV?") + return self.recv() + + def get_trcp(self): + """Sets the coupling mode of the specified trigger source.""" + self.send("TRCP?") + return self.recv() + + def set_trdl(self, val): + """Sets the time delay at which the trigger occurs.""" + val = str(val) + self.send("TRDL " + val) + + def get_trdl(self): + """Read the time delay at which the trigger is to occur.""" + self.send("TRDL?") + return self.recv() + + def get_trpa(self): + """Sets the time at which the trigger is to occur.""" + self.send("TRPA?") + return self.recv() + + def get_trsl(self): + """Sets the time at which the trigger is to occur.""" + self.send("TRSL?") + return self.recv() + + def set_tdiv(self, val): + """ + Modifies the timebase setting. + Example: set_tdiv(1e-6) + """ + val = str(val) + self.send("TDIV " + val) + + def get_tdiv(self): + """Read the timebase setting.""" + msg = "TDIV?" + self.send(msg) + return self.recv() + + def set_vdiv(self, ch, val): + """ + Sets the vertical sensitivity. + Example: set_vdiv("C1",5) + """ + val = str(val) + self.send(ch + ":VDIV " + val) + + def get_vdiv(self, ch): + """ + Read the vertical sensitivity. + Example: get_vdiv("C1") + """ + self.send(ch + ":VDIV?") + return self.recv() + + def set_ofst(self, ch, val): + """ + Allows output channel vertical offset adjustment. + Example: set_ofst("C1",2) + """ + val = str(val) + self.send(ch + ":OFST " + val) + + def get_ofst(self, ch): + """ + Read output channel vertical offset adjustment. + Example: get_ofst("C1") + """ + self.send(ch + ":OFST?") + # print repr(self._comm.recv()) + return self.recv() + + def set_cpl(self, ch, cpl): + """ + Selects the specified input channel's coupling mode. + Example: set_cpl("C1",{"D[{1M,50}]","A[{1M,50}]","GND"}) + """ + cpl = str(cpl) + lenCpl = len(cpl) + + if lenCpl == 1: + self.send(ch + ":CPL " + cpl + "1M") + elif lenCpl == 3: + self.send(ch + ":CPL " + cpl) + + self.send(ch + ":CPL?") + # print repr(self._comm.recv()) + + def get_cpl(self, ch): + """ + Read the specified input channel's coupling mode. + Example: get_cpl("C1") + """ + self.send(ch + ":CPL?") + # print repr(self._comm.recv()) + return self.recv() + + def set_waveformFormat(self, dataFormat): + """ + Selects the format the oscilloscope uses to send waveform data. Cord is fixed to \"LOW\". + Example: set_waveformFormat({"WORD","BYTE"}) + """ + self.send("CFMT DEF9," + dataFormat + ",BIN") + self.send("CORD LO") + + def get_waveformFormat(self): + """ + Read the selected format (CFMT, CORD) used by the oscilloscope to send waveform data. + """ + self.send("CFMT?") + cfmt = self.recv() + self.send("CORD?") + cord = self.recv() + return cfmt, cord + + def set_waveformSetup(self, sp=256, np=0, fp=0, sn=0): + """ + Selected the amount of data in a waveform to be transmitted (WFSU). + Default: sp=256,np=0,fp=0,sn=0 + """ + sp = str(sp) + np = str(np) + fp = str(fp) + sn = str(sn) + self.send("WFSU SP," + sp + ",NP," + np + ",FP," + fp + ",SN," + sn) + + def get_waveformSetup(self): + """ + Selected the amount of data in a waveform to be transmitted (WFSU). + Default: sp=0,np=0,fp=0,sn=0 + """ + self.send("WFSU?") + # string values + result = self.recv() + # int values + return result + return map(int, result.split(",")[1::2]) + + def get_inr(self): + """ + Return the INR status. + """ + self.send("INR?") + inr = self.recv() + inr = inr[3:] + inr = int(inr) + return inr + + def get_opc(self): + """ + Return the INR status. + """ + self.send("*OPC?") + opc = self.recv() + opc = opc[4:] + opc = int(opc) + return opc + + def set_memory_size(self, val): + """ + Modifies the memory size setting. + Example: set_memory_size(1e3) + """ + val = str(val) + self.send("MEMORY_SIZE " + val) + + def get_memory_size(self): + """Read the memory size setting.""" + self.send("MEMORY_SIZE?") + ans = self.recv() + return ans + a, value, c = ans.split(" ") + value = float(value) + return value + + def set_math_setup_sweeps(self, args): + """ + Set for the mathematical function selected with the number of sweeps. + Example: set_math_setup_sweeps("F1", "128") + """ + mathChan = args[0] + Sweeps = args[1] + command = "VBS app.Math." + mathChan + ".Operator1Setup.Sweeps=" + Sweeps + self.send(command) + + def get_math_setup_sweeps(self, mathChan): + """ + Read the number of sweeps for the selected mathematical function. + Example: get_math_setup_sweeps("F1") + """ + command = "VBS? RETURN=app.Math." + mathChan + ".Operator1Setup.Sweeps" + self.send(command) + ans = self.recv() + a, value = ans.split(" ") + return value + + def get_math_out_sweeps(self, mathChan): + """ + Read the number of executed sweeps for the selected mathematical function. + Example: get_math_out_sweeps("F1") + """ + command = "VBS? RETURN=app.Math." + mathChan + ".Out.Result.Sweeps" + self.send(command) + ans = self.recv() + a, value = ans.split(" ") + return value + + def set_smart_memory(self, memoryMode): + """ + Sets the mode of memory management to one of the two modes: + + SetMaximumMemory – Maximizes the memory length for the given timebase setting; limited by the + maximum length that is compatible with the maximum sampling rate that the DSO can achieve. + + FixedSampleRate – Keeps the sampling rate the same when the timebase is changed; limited by the + maximum sampling rate that the DSO can achieve. + Example: set_smart_memory("SetMaximumMemory") + or set_smart_memory("FixedSampleRate") + """ + command = ( + "VBS app.Acquisition.Horizontal.SmartMemory = " + '"' + memoryMode + '"' + ) + self.send(command) + + def get_smart_memory(self): + """ + Read the mode of memory management. + """ + command = "VBS? RETURN=app.Acquisition.Horizontal.SmartMemory" + self.send(command) + ans = self.recv() + a, value = ans.split(" ") + return value + + def set_sample_rate(self, sampleRate): + """ + Queries the sample rate of the ADCs. + If random interleaved sampling (RIS) is in use, this value will be + less than the effective sampling rate of the traces. + + Range: From 500 to 1e+010, step 1e+008, locked to 1 2.5 5 + + Example: set_sample_rate(2e6) + """ + sampleRate = str(sampleRate) + command = ( + "VBS app.Acquisition.Horizontal.SampleRate = " + '"' + sampleRate + '"' + ) + self.send(command) + + def get_sample_rate(self): + """ + Queries the sample rate of the ADCs. + If random interleaved sampling (RIS) is in use, this value will be + less than the effective sampling rate of the traces. + """ + command = "VBS? RETURN=app.Acquisition.Horizontal.SampleRate" + self.send(command) + ans = self.recv() + a, value = ans.split(" ") + value = int(value) + return value + + def get_math_measure_control_reference(self, MathMeaschan): + """ + Read the math/measure controle reference.. + Example: get_math_measure_control_reference("F1") returns, Average, Histrogram, etc. + """ + command = "VBS? RETURN=app.Math." + MathMeaschan + ".Operator1" + self.send(command) + ans = self.recv() + a, value = ans.split(" ") + return value + + def get_math_histogram_setup_value(self, mathChan): + """ + Read the mathematical histogram value. + Example: get_math_histogram_value("F1") returns an integer like 2000. + """ + command = "VBS? RETURN=app.Math." + mathChan + ".Operator1Setup.Values" + self.send(command) + ans = self.recv() + a, value = ans.split(" ") + return value + + def get_math_histogram_population_inside(self, mathChan): + """ + Read the number of population histogram in the buffer. + Example: get_math_out_sweeps("F4") returns an integer like 2000. + """ + command = "VBS? RETURN=app.Math." + mathChan + ".Out.Result.PopulationInside" + self.send(command) + ans = self.recv() + a, value = ans.split(" ") + return value + + def format_waveform(self, recvd, ch): + all = [] + all.append(recvd) + all = b"".join(all) + if all.endswith(b"\n"): + all = all[:-1] + waveStart = 21 + waveDescLength = int(all[18:21]) + arrayAll = np.fromstring(all[waveDescLength + waveStart :], dtype=np.int16) + # data in the array + # self.arrayListChannel[ch]= arrayAll + # self.dArrayData[ch] = arrayAll + + CHANNEL = ch + wavedescAll = struct.unpack( + "<16s16shhiiiiiiiiii16si16shhiiiiiiiiihhffffhhfdd48s48sf16sfHHhhHHfHHffH", + all[waveStart : waveStart + 346], + ) + descriptor_name = wavedescAll[0].replace(b"\x00", b"") + template_name = wavedescAll[1].replace(b"\x00", b"") + dComm_type = {"0": "BYTE", "1": "WORD"} + COMM_TYPE = dComm_type.get(str(wavedescAll[2])) + dComm_order = {"0": "HIFIRST", "1": "LOFIRST"} + COMM_ORDER = dComm_order.get(str(wavedescAll[3])) + WAVE_DESCRIPTOR = str(wavedescAll[4]) + WAVE_ARRAY_1 = str(wavedescAll[10]) + INSTRUMENT_NAME = wavedescAll[14].replace(b"\x00", b"") + INSTRUMENT_NUMBER = str(wavedescAll[15]) + # WAVE_ARRAY_COUNT is not wavedescAll[19] + # because the 3 fist points are bad + arrayAllSize = len(arrayAll) + WAVE_ARRAY_COUNT = str(arrayAllSize) + PNTS_PER_SCREEN = str(wavedescAll[20]) + FIRST_VALID_PNT = str(wavedescAll[21]) + LAST_VALID_PNT = str(wavedescAll[22]) + FIRST_POINT = str(wavedescAll[23]) + SPARSING_FACTOR = str(wavedescAll[24]) + SEGMENT_INDEX = str(wavedescAll[25]) + SUBARRAY_COUNT = str(wavedescAll[26]) + SWEEPS_PER_ACQ = str(wavedescAll[27]) + POINTS_PER_PAIR = str(wavedescAll[28]) + PAIR_OFFSET = str(wavedescAll[29]) + VERTICAL_GAIN = str(wavedescAll[30]) + VERTICAL_OFFSET = str(wavedescAll[31]) + MAX_VALUE = str(wavedescAll[32]) + MIN_VALUE = str(wavedescAll[33]) + NOMINAL_BITS = str(wavedescAll[34]) + NOM_SUBARRAY_COUNT = str(wavedescAll[35]) + HORIZ_INTERVAL = str(wavedescAll[36]) + HORIZ_OFFSET = str(wavedescAll[37]) + PIXEL_OFFSET = str(wavedescAll[38]) + VERTUNIT = wavedescAll[39].replace(b"\x00", b"") + HORUNIT = wavedescAll[40].replace(b"\x00", b"") + HORIZ_UNCERTAINTY = str(wavedescAll[41]) + TRIGGER_TIME = struct.unpack("dbbbbhh", wavedescAll[42]) # time_stamp + s = str(TRIGGER_TIME[0]) + min = str(TRIGGER_TIME[1]) + h = str(TRIGGER_TIME[2]) + dd = str(TRIGGER_TIME[3]) + mm = str(TRIGGER_TIME[4]) + yyyy = str(TRIGGER_TIME[5]) + ACQ_DURATION = str(wavedescAll[43]) + dRecord_type = { + "0": "single_sweep", + "1": "interleaved", + "2": "histogram", + "3": "graph", + "4": "filter_coefficient", + "5": "complex", + "6": "extrema", + "7": "sequence_obsolete", + "8": "centered_RIS", + "9": "peak_detect", + } + RECORD_TYPE = dRecord_type.get(str(wavedescAll[44])) + dPocessing_done = { + "0": "no_processing", + "1": "fir_filter", + "2": "interpolated", + "3": "sparsed", + "4": "auto_scaled", + "5": "no_result", + "6": "rolling", + "7": "cumulative", + } + PROCESSING_DONE = dPocessing_done.get(str(wavedescAll[45])) + dTime_base = { + "0": "1_ps/div", + "1": "2_ps/div", + "2": "5_ps/div", + "3": "10_ps/div", + "4": "20_ps/div", + "5": "50_ps/div", + "6": "100_ps/div", + "7": "200_ps/div", + "8": "500_ps/div", + "9": "1_ns/div", + "10": "2_ns/div", + "11": "5_ns/div", + "12": "10_ns/div", + "13": "20_ns/div", + "14": "50_ns/div", + "15": "100_ns/div", + "16": "200_ns/div", + "17": "500_ns/div", + "18": "1_us/div", + "19": "2_us/div", + "20": "5_us/div", + "21": "10_us/div", + "22": "20_us/div", + "23": "50_us/div", + "24": "100_us/div", + "25": "200_us/div", + "26": "500_us/div", + "27": "1_ms/div", + "28": "2_ms/div", + "29": "5_ms/div", + "30": "10_ms/div", + "31": "20_ms/div", + "32": "50_ms/div", + "33": "100_ms/div", + "34": "200_ms/div", + "35": "500_ms/div", + "36": "1_s/div", + "37": "2_s/div", + "38": "5_s/div", + "39": "10_s/div", + "40": "20_s/div", + "41": "50_s/div", + "42": "100_s/div", + "43": "200_s/div", + "44": "500_s/div", + "45": "1_ks/div", + "46": "2_ks/div", + "47": "5_ks/div", + "100": "EXTERNAL", + } + TIME_BASE = dTime_base.get(str(wavedescAll[48])) + dVert_coupling = { + "0": "DC_50_Ohm", + "1": "ground", + "2": "DC_1MOhm", + "3": "ground", + "4": "AC_1MOhm", + } + VERT_COUPLING = dVert_coupling.get(str(wavedescAll[49])) + PROBE_ATT = str(wavedescAll[50]) + dFixed_vert_gain = { + "0": "1_uv/div", + "1": "2_uv/div", + "2": "5_uv/div", + "3": "10_uv/div", + "4": "20_uv/div", + "5": "50_uV/div", + "6": "100_uV/div", + "7": "200_uV/div", + "8": "500_uV/div", + "9": "1_mV/div", + "10": "2_mV/div", + "11": "5_mV/div", + "12": "10_mV/div", + "13": "20_mV/div", + "14": "50_mV/div", + "15": "100_mV/div", + "16": "200_mV/div", + "17": "500_mV/div", + "18": "1_V/div", + "19": "2_V/div", + "20": "5_V/div", + "21": "10_V/div", + "22": "20_V/div", + "23": "50_V/div", + "24": "100_V/div", + "25": "200_V/div", + "26": "500_V/div", + "27": "1_kV/div", + } + FIXED_VERT_GAIN = dFixed_vert_gain.get(str(wavedescAll[51])) + BANDWIDTH_LIMIT = str(wavedescAll[52]) + dBandwidth_limit = {"0": "off", "1": "on"} + BANDWIDTH_LIMIT = dBandwidth_limit.get(str(wavedescAll[52])) + VERTICAL_VERNIER = str(wavedescAll[53]) + ACQ_VERT_VERNIER = str(wavedescAll[54]) + # dWave_source = {'0':'CHANNEL_1', '1':'CHANNEL_2', '2':'CHANNEL_3', '3':'CHANNEL_4', '9':'UNKNOWN', + # 'NONE':'UNKNOWN'} + # WAVE_SOURCE = dWave_source.get(str(wavedescAll[55])) + WAVE_SOURCE = ch + # print("WAVE_SOURCE parameter = {0}".format(wavedescAll[55])) + + # ===== Header ===== + f = io.StringIO() + f.write("==== CHANNEL PARAMETERS: " + ch + " ====\n") + f.write("DESCRIPTOR_NAME : " + descriptor_name.decode() + "\n") + f.write("TEMPLATE_NAME : " + template_name.decode() + "\n") + f.write("COMM_TYPE : " + COMM_TYPE + "\n") + f.write("COMM_ORDER : " + COMM_ORDER + "\n") + f.write("WAVE_DESCRIPTOR : " + WAVE_DESCRIPTOR + "\n") + f.write("WAVE_ARRAY_1 : " + WAVE_ARRAY_1 + "\n") + f.write("INSTRUMENT_NAME : " + INSTRUMENT_NAME.decode() + "\n") + f.write("INSTRUMENT_NUMBER : " + INSTRUMENT_NUMBER + "\n") + f.write("WAVE_ARRAY_COUNT : = " + WAVE_ARRAY_COUNT + "\n") + f.write("PNTS_PER_SCREEN : " + PNTS_PER_SCREEN + "\n") + f.write("FIRST_VALID_PNT : " + FIRST_VALID_PNT + "\n") + f.write("LAST_VALID_PNT : " + LAST_VALID_PNT + "\n") + f.write("FIRST_POINT : " + FIRST_POINT + "\n") + f.write("SPARSING_FACTOR : " + SPARSING_FACTOR + "\n") + f.write("SEGMENT_INDEX : " + SEGMENT_INDEX + "\n") + f.write("SUBARRAY_COUNT : " + SUBARRAY_COUNT + "\n") + f.write("SWEEPS_PER_ACQ : " + SWEEPS_PER_ACQ + "\n") + f.write("POINTS_PER_PAIR : " + POINTS_PER_PAIR + "\n") + f.write("PAIR_OFFSET : " + PAIR_OFFSET + "\n") + f.write("VERTICAL_GAIN : " + VERTICAL_GAIN + "\n") + f.write("VERTICAL_OFFSET : " + VERTICAL_OFFSET + "\n") + f.write("MAX_VALUE : " + MAX_VALUE + "\n") + f.write("MIN_VALUE : " + MIN_VALUE + "\n") + f.write("NOMINAL_BITS : " + NOMINAL_BITS + "\n") + f.write("NOM_SUBARRAY_COUNT : " + NOM_SUBARRAY_COUNT + "\n") + f.write("HORIZ_INTERVAL : " + HORIZ_INTERVAL + "\n") + f.write("HORIZ_OFFSET : " + HORIZ_OFFSET + "\n") + f.write("PIXEL_OFFSET : " + PIXEL_OFFSET + "\n") + f.write("VERTUNIT : " + VERTUNIT.decode() + "\n") + f.write("HORUNIT : " + HORUNIT.decode() + "\n") + f.write("HORIZ_UNCERTAINTY : " + HORIZ_UNCERTAINTY + "\n") + f.write( + "TRIGGER_TIME : date = " + + yyyy + + "-" + + mm + + "-" + + dd + + ", time = " + + h + + ":" + + min + + ":" + + s + + "\n" + ) + f.write("ACQ_DURATION : " + ACQ_DURATION + "\n") + f.write("RECORD_TYPE : " + RECORD_TYPE + "\n") + f.write("PROCESSING_DONE : " + PROCESSING_DONE + "\n") + f.write("TIME_BASE : " + TIME_BASE + "\n") + f.write("VERT_COUPLING : " + VERT_COUPLING + "\n") + f.write("PROBE_ATT : " + PROBE_ATT + "\n") + f.write("FIXED_VERT_GAIN : " + FIXED_VERT_GAIN + "\n") + f.write("BANDWIDTH_LIMIT : " + BANDWIDTH_LIMIT + "\n") + f.write("VERTICAL_VERNIER : " + VERTICAL_VERNIER + "\n") + f.write("ACQ_VERT_VERNIER : " + ACQ_VERT_VERNIER + "\n") + f.write("WAVE_SOURCE : " + WAVE_SOURCE + "\n") + f.write("\n") + # np.savetxt(f,arrayAll,fmt='%i') + + return f.getvalue(), arrayAllSize, arrayAll + + def ask_waveform(self, ch): + tdiv = float(self.get_tdiv().split()[1]) + cptr_time = tdiv * 10 + 1 + self.send(ch + ":WF? ALL") + time.sleep(cptr_time) + + def answer_waveform(self, ch): + recvd = self.recv(False) + return self.format_waveform(recvd, ch) + + def get_waveform(self, ch): + """ + Transfers a waveform data with the selected format according to CFMT, CORD and WFSU. + Returns the scope parameters and the data waveform in a string. + Example: get_waveform("C1") + """ + + self.ask_waveform(ch) + return self.answer_waveform(ch) -- GitLab From 647df2bd6471862b25c6f7135fc4673b4da31763 Mon Sep 17 00:00:00 2001 From: Jibril Mammeri Date: Fri, 5 Mar 2021 16:50:39 +0100 Subject: [PATCH 2/3] Inherit Oscilloscope class, define counter --- bliss/controllers/lecroy.py | 225 +++++++++++++++++++++++++++--- bliss/controllers/oscilloscope.py | 5 +- 2 files changed, 209 insertions(+), 21 deletions(-) diff --git a/bliss/controllers/lecroy.py b/bliss/controllers/lecroy.py index 0e40968436..73e3391df8 100644 --- a/bliss/controllers/lecroy.py +++ b/bliss/controllers/lecroy.py @@ -7,24 +7,74 @@ # Distributed under the GNU LGPLv3. See LICENSE for more info. import time +import numpy +import gevent import numpy as np import io import socket import struct +from bliss.common.utils import autocomplete_property +from bliss.controllers.oscilloscope import ( + Oscilloscope, + OscilloscopeHardwareController, + OscilloscopeAnalogChannel, + OscAnalogChanData, + OscMeasData, + OscilloscopeTrigger, + OscilloscopeCounterController, +) HEADER_FORMAT = ">BBBBL" DATA_FLAG = 0x80 - - -class Lecroy620zi(object): +HEADER_LIST = [ + "WAVE_DESCRIPTOR", + "WAVE_ARRAY_1", + "INSTRUMENT_NUMBER", + "PNTS_PER_SCREEN", + "FIRST_VALID_PNT", + "LAST_VALID_PNT", + "FIRST_POINT", + "SPARSING_FACTOR", + "SEGMENT_INDEX", + "SUBARRAY_COUNT", + "SWEEPS_PER_ACQ", + "POINTS_PER_PAIR", + "PAIR_OFFSET", + "VERTICAL_GAIN", + "VERTICAL_OFFSET", + "MAX_VALUE", + "MIN_VALUE", + "NOMINAL_BITS", + "NOM_SUBARRAY_COUNT", + "HORIZ_INTERVAL", + "HORIZ_OFFSET", + "PIXEL_OFFSET", + "HORIZ_UNCERTAINTY", + "ACQ_DURATION", + "PROBE_ATT", + "VERTICAL_VERNIER", + "ACQ_VERT_VERNIER", +] + + +class LecroyOscCtrl(OscilloscopeHardwareController): """ Define the methods used with the Lecroy scope Wave Runner 620Zi. """ - def __init__(self, host, port=1861, timeout=5.0): + def __init__(self, config): + host = config["host"] + port = 1861 + if "timeout" in config: + timeout = config["timeout"] + else: + timeout = 5.0 + self.counters = config["counters"] self._comm = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._comm.connect((host, port)) self._comm.settimeout(timeout) + self.done = True + self.set_waveformFormat("WORD") def send(self, msg): if not msg.endswith("\n"): @@ -34,22 +84,111 @@ class Lecroy620zi(object): self._comm.sendall(b"".join([header, msg])) def recv(self, decode=True): - dtstr = [] + """Return a message from the scope.""" + reply = [] while True: - data = self._comm.recv(8) - head_data = struct.unpack("B3BI", data) # get response (header from device) - lnt = socket.ntohl(head_data[-1]) # data length to be captured - flg = head_data[0] - dtstr.append(self._comm.recv(lnt)) - if flg != DATA_FLAG: # data flag 0x80 + header = [] + + while len(b"".join(header)) < 8: + header.append(self._comm.recv(8 - len(b"".join(header)))) + + operation, headerver, seqnum, spare, totalbytes = struct.unpack( + HEADER_FORMAT, b"".join(header) + ) + + buffer = [] + + while len(b"".join(buffer)) < totalbytes: + buffer.append(self._comm.recv(totalbytes - len(b"".join(buffer)))) + + reply.append(b"".join(buffer)) + + if operation % 2: break - answr = b"".join(dtstr) + + answr = b"".join(reply) if decode: answr = answr.decode() if answr.endswith("\n"): answr = answr[:-1] + return answr + # def recv2(self, decode=True): + # dtstr = [] + # while True: + # data = self._comm.recv(8) + # head_data = struct.unpack("B3BI", data) # get response (header from device) + # lnt = socket.ntohl(head_data[-1]) # data length to be captured + # flg = head_data[0] + # dtstr.append(self._comm.recv(lnt)) + # if flg != DATA_FLAG: # data flag 0x80 + # break + # answr = b"".join(dtstr) + # if decode: + # answr = answr.decode() + # if answr.endswith("\n"): + # answr = answr[:-1] + # return answr + + def wait_ready(self, timeout=5): + with gevent.timeout.Timeout(timeout): + while not self.done: + gevent.sleep(.1) + + def acq_read_channel(self, name, length=None): + # Wait for trigger... + # still to be handled... + + header, length, data = self.get_waveform(name) + header = self.header_to_dict(header) + self.header = header + raw_data = data + data = data.astype(numpy.float) + float(header["PIXEL_OFFSET"]) + + return OscAnalogChanData(length, raw_data, data, header) + + def get_channel_names(self): + channels_names = [] + for dic in self.counters: + channels_names.append(dic["counter_name"]) + return channels_names + + def get_measurement_names(self): + return HEADER_LIST + + def acq_prepare(self): + self.set_waveformFormat("WORD") + + def acq_read_measurement(self, ch): + return OscMeasData(float(self.header[ch]), ch) + + def acq_start(self): + pass + + def acq_done(self): + return self.done + + def header_to_dict(self, header_string): + res = {} + for entry in header_string.strip("====").split("\n"): + if " : " in entry: + key, value = entry.split(" : ") + res[key] = value + if " : " in entry: + key, value = entry.split(" : ") + res[key] = value + return res + + def stop(self): + pass + + def read(self): + pass + + def __close__(self): + self._comm.close() + def rst(self): """Initiate a device reset.""" self.send("*RST") @@ -206,7 +345,7 @@ class Lecroy620zi(object): def set_waveformSetup(self, sp=256, np=0, fp=0, sn=0): """ - Selected the amount of data in a waveform to be transmitted (WFSU). + Select the amount of data in a waveform to be transmitted (WFSU). Default: sp=256,np=0,fp=0,sn=0 """ sp = str(sp) @@ -217,7 +356,7 @@ class Lecroy620zi(object): def get_waveformSetup(self): """ - Selected the amount of data in a waveform to be transmitted (WFSU). + Get the amount of data in a waveform to be transmitted (WFSU). Default: sp=0,np=0,fp=0,sn=0 """ self.send("WFSU?") @@ -239,7 +378,7 @@ class Lecroy620zi(object): def get_opc(self): """ - Return the INR status. + Return state of last operation. """ self.send("*OPC?") opc = self.recv() @@ -282,8 +421,8 @@ class Lecroy620zi(object): command = "VBS? RETURN=app.Math." + mathChan + ".Operator1Setup.Sweeps" self.send(command) ans = self.recv() - a, value = ans.split(" ") - return value + # a, value = ans.split(" ") + return ans def get_math_out_sweeps(self, mathChan): """ @@ -641,7 +780,7 @@ class Lecroy620zi(object): def ask_waveform(self, ch): tdiv = float(self.get_tdiv().split()[1]) - cptr_time = tdiv * 10 + 1 + cptr_time = tdiv * 10.0 + 0.1 self.send(ch + ":WF? ALL") time.sleep(cptr_time) @@ -656,5 +795,53 @@ class Lecroy620zi(object): Example: get_waveform("C1") """ + self.done = False self.ask_waveform(ch) - return self.answer_waveform(ch) + answer = self.answer_waveform(ch) + self.done = True + return answer + + +class LecroyAnalogChannel(OscilloscopeAnalogChannel): + pass + + +class LecroyOsc(Oscilloscope): + # user exposed object + def __init__(self, name, config): + self._device = LecroyOscCtrl(config) + Oscilloscope.__init__(self, name, config) + + def _channel_counter(self, name): + return LecroyAnalogChannel(name, self._counter_controller) + + def __close__(self): + self._device.__close__() + + @autocomplete_property + def trigger(self): + return LecroyTrigger(self._device) + + +class LecroyTrigger(OscilloscopeTrigger): + def __info__(self): + return ( + f"trigger info \n" + + f"source: {self._device.get_trmd()}" + # + f"type: {current['TYPE']}" + ) + + def _get_settings(self): + raise NotImplementedError + + def set_trigger_setting(self, param, value): + raise NotImplementedError + + +# class LecroyCntr(OscilloscopeCounterController): +# def __init__(self, scope): +# OscilloscopeCounterController.__init__(self, scope) +# +# @property +# def counters(self): +# return self._scope.counters diff --git a/bliss/controllers/oscilloscope.py b/bliss/controllers/oscilloscope.py index e2d1881771..5cbf8227a2 100644 --- a/bliss/controllers/oscilloscope.py +++ b/bliss/controllers/oscilloscope.py @@ -6,7 +6,7 @@ # Distributed under the GNU LGPLv3. See LICENSE for more info. from bliss.common.utils import autocomplete_property -from bliss.controllers.counter import CounterController +from bliss.controllers.counter import CounterController, CounterContainer from bliss.common.counter import Counter from bliss.scanning.acquisition.oscilloscope import ( OscilloscopeAcquisitionSlave, @@ -16,10 +16,11 @@ from bliss.scanning.acquisition.oscilloscope import ( from bliss.controllers.counter import counter_namespace -class Oscilloscope: +class Oscilloscope(CounterContainer): """Base class for user level objects for oscilloscopes""" def __init__(self, name, config): + super().__init__() # inhereted class should initialise OscilloscopeController that holds the comm as self._device before getting here assert self._device is not None -- GitLab From c146c88e5cf2901a42e36b888dbc8705f4ca4865 Mon Sep 17 00:00:00 2001 From: Jibril Mammeri Date: Wed, 10 Mar 2021 09:36:06 +0100 Subject: [PATCH 3/3] Create oscilloscope module --- bliss/controllers/oscilloscope/__init__.py | 6 ++ .../{oscilloscope.py => oscilloscope/base.py} | 2 +- .../controllers/{ => oscilloscope}/lecroy.py | 87 ++++--------------- .../{ => oscilloscope}/tektronix.py | 4 +- bliss/scanning/acquisition/oscilloscope.py | 4 +- doc/docs/config_lecroy.md | 63 ++++++++++++++ doc/mkdocs.yml | 1 + tests/test_configuration/tektronix.yml | 2 +- 8 files changed, 94 insertions(+), 75 deletions(-) create mode 100644 bliss/controllers/oscilloscope/__init__.py rename bliss/controllers/{oscilloscope.py => oscilloscope/base.py} (99%) rename bliss/controllers/{ => oscilloscope}/lecroy.py (92%) rename bliss/controllers/{ => oscilloscope}/tektronix.py (98%) create mode 100644 doc/docs/config_lecroy.md diff --git a/bliss/controllers/oscilloscope/__init__.py b/bliss/controllers/oscilloscope/__init__.py new file mode 100644 index 0000000000..28a060d8bf --- /dev/null +++ b/bliss/controllers/oscilloscope/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the bliss project +# +# Copyright (c) 2015-2021 Beamline Control Unit, ESRF +# Distributed under the GNU LGPLv3. See LICENSE for more info. diff --git a/bliss/controllers/oscilloscope.py b/bliss/controllers/oscilloscope/base.py similarity index 99% rename from bliss/controllers/oscilloscope.py rename to bliss/controllers/oscilloscope/base.py index 5cbf8227a2..a55519abbc 100644 --- a/bliss/controllers/oscilloscope.py +++ b/bliss/controllers/oscilloscope/base.py @@ -2,7 +2,7 @@ # # This file is part of the bliss project # -# Copyright (c) 2015-2020 Beamline Control Unit, ESRF +# Copyright (c) 2015-2021 Beamline Control Unit, ESRF # Distributed under the GNU LGPLv3. See LICENSE for more info. from bliss.common.utils import autocomplete_property diff --git a/bliss/controllers/lecroy.py b/bliss/controllers/oscilloscope/lecroy.py similarity index 92% rename from bliss/controllers/lecroy.py rename to bliss/controllers/oscilloscope/lecroy.py index 73e3391df8..72fda9a434 100644 --- a/bliss/controllers/lecroy.py +++ b/bliss/controllers/oscilloscope/lecroy.py @@ -3,7 +3,7 @@ # # This file is part of the bliss project # -# Copyright (c) 2015-2020 Beamline Control Unit, ESRF +# Copyright (c) 2015-2021 Beamline Control Unit, ESRF # Distributed under the GNU LGPLv3. See LICENSE for more info. import time @@ -14,7 +14,7 @@ import io import socket import struct from bliss.common.utils import autocomplete_property -from bliss.controllers.oscilloscope import ( +from bliss.controllers.oscilloscope.base import ( Oscilloscope, OscilloscopeHardwareController, OscilloscopeAnalogChannel, @@ -26,35 +26,6 @@ from bliss.controllers.oscilloscope import ( HEADER_FORMAT = ">BBBBL" DATA_FLAG = 0x80 -HEADER_LIST = [ - "WAVE_DESCRIPTOR", - "WAVE_ARRAY_1", - "INSTRUMENT_NUMBER", - "PNTS_PER_SCREEN", - "FIRST_VALID_PNT", - "LAST_VALID_PNT", - "FIRST_POINT", - "SPARSING_FACTOR", - "SEGMENT_INDEX", - "SUBARRAY_COUNT", - "SWEEPS_PER_ACQ", - "POINTS_PER_PAIR", - "PAIR_OFFSET", - "VERTICAL_GAIN", - "VERTICAL_OFFSET", - "MAX_VALUE", - "MIN_VALUE", - "NOMINAL_BITS", - "NOM_SUBARRAY_COUNT", - "HORIZ_INTERVAL", - "HORIZ_OFFSET", - "PIXEL_OFFSET", - "HORIZ_UNCERTAINTY", - "ACQ_DURATION", - "PROBE_ATT", - "VERTICAL_VERNIER", - "ACQ_VERT_VERNIER", -] class LecroyOscCtrl(OscilloscopeHardwareController): @@ -65,11 +36,9 @@ class LecroyOscCtrl(OscilloscopeHardwareController): def __init__(self, config): host = config["host"] port = 1861 - if "timeout" in config: - timeout = config["timeout"] - else: - timeout = 5.0 + timeout = config.get("timeout", 5) self.counters = config["counters"] + self.measurements = config["measurements"] self._comm = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._comm.connect((host, port)) self._comm.settimeout(timeout) @@ -85,51 +54,31 @@ class LecroyOscCtrl(OscilloscopeHardwareController): def recv(self, decode=True): """Return a message from the scope.""" - reply = [] + answr = b"" while True: - header = [] - - while len(b"".join(header)) < 8: - header.append(self._comm.recv(8 - len(b"".join(header)))) - + header = b"" + while len(header) < 8: + header += self._comm.recv(8 - len(header)) operation, headerver, seqnum, spare, totalbytes = struct.unpack( - HEADER_FORMAT, b"".join(header) + HEADER_FORMAT, header[:8] ) + buffer = b"" + while len(buffer) < totalbytes: + buffer += self._comm.recv(totalbytes - len(buffer)) - buffer = [] - - while len(b"".join(buffer)) < totalbytes: - buffer.append(self._comm.recv(totalbytes - len(b"".join(buffer)))) - - reply.append(b"".join(buffer)) - + answr += buffer if operation % 2: break - answr = b"".join(reply) if decode: answr = answr.decode() if answr.endswith("\n"): answr = answr[:-1] - return answr - # def recv2(self, decode=True): - # dtstr = [] - # while True: - # data = self._comm.recv(8) - # head_data = struct.unpack("B3BI", data) # get response (header from device) - # lnt = socket.ntohl(head_data[-1]) # data length to be captured - # flg = head_data[0] - # dtstr.append(self._comm.recv(lnt)) - # if flg != DATA_FLAG: # data flag 0x80 - # break - # answr = b"".join(dtstr) - # if decode: - # answr = answr.decode() - # if answr.endswith("\n"): - # answr = answr[:-1] - # return answr + def command(self, msg): + self.send(msg) + return self.recv() def wait_ready(self, timeout=5): with gevent.timeout.Timeout(timeout): @@ -144,7 +93,7 @@ class LecroyOscCtrl(OscilloscopeHardwareController): header = self.header_to_dict(header) self.header = header raw_data = data - data = data.astype(numpy.float) + float(header["PIXEL_OFFSET"]) + # data = data.astype(numpy.float) + float(header["PIXEL_OFFSET"]) return OscAnalogChanData(length, raw_data, data, header) @@ -155,7 +104,7 @@ class LecroyOscCtrl(OscilloscopeHardwareController): return channels_names def get_measurement_names(self): - return HEADER_LIST + return [measurement["measurement_name"] for measurement in self.measurements] def acq_prepare(self): self.set_waveformFormat("WORD") diff --git a/bliss/controllers/tektronix.py b/bliss/controllers/oscilloscope/tektronix.py similarity index 98% rename from bliss/controllers/tektronix.py rename to bliss/controllers/oscilloscope/tektronix.py index 8c473d167a..ba9e581e87 100644 --- a/bliss/controllers/tektronix.py +++ b/bliss/controllers/oscilloscope/tektronix.py @@ -2,13 +2,13 @@ # # This file is part of the bliss project # -# Copyright (c) 2015-2020 Beamline Control Unit, ESRF +# Copyright (c) 2015-2021 Beamline Control Unit, ESRF # Distributed under the GNU LGPLv3. See LICENSE for more info. # for inspiration have a look at # https://github.com/tektronix/Programmatic-Control-Examples/tree/master/Examples/Oscilloscopes -from bliss.controllers.oscilloscope import ( +from bliss.controllers.oscilloscope.base import ( Oscilloscope, OscilloscopeHardwareController, OscilloscopeAnalogChannel, diff --git a/bliss/scanning/acquisition/oscilloscope.py b/bliss/scanning/acquisition/oscilloscope.py index ecb24db4f8..09e24c0c0b 100644 --- a/bliss/scanning/acquisition/oscilloscope.py +++ b/bliss/scanning/acquisition/oscilloscope.py @@ -38,7 +38,7 @@ class OscilloscopeAcquisitionSlave(BaseCounterAcquisitionSlave): ) def _do_add_counter(self, counter): - from bliss.controllers.oscilloscope import OscilloscopeAnalogChannel + from bliss.controllers.oscilloscope.base import OscilloscopeAnalogChannel super()._do_add_counter(counter) # add the 'default' counter @@ -66,7 +66,7 @@ class OscilloscopeAcquisitionSlave(BaseCounterAcquisitionSlave): def reading(self): data = [numpy.nan] * len(self.channels) # is this really needed? - from bliss.controllers.oscilloscope import ( + from bliss.controllers.oscilloscope.base import ( OscilloscopeAnalogChannel, OscilloscopeMeasurement, ) diff --git a/doc/docs/config_lecroy.md b/doc/docs/config_lecroy.md new file mode 100644 index 0000000000..ad3e73b7f1 --- /dev/null +++ b/doc/docs/config_lecroy.md @@ -0,0 +1,63 @@ +# Lecroy + +## Description + +This module allows to control Lecroy Oscilloscopes, it provides access to the +device commands and allows to use it as a counter during a scan. + + +## Configuration + +### YAML configuration file example + + +```YAML + plugin: bliss + module: oscilloscope.lecroy + class: LecroyOsc + name: l1 + host: uwoisgspectro + counters: + - counter_name: C1 + channel: 1 + - counter_name: F1 + channel: 1 + measurements: + - measurement_name: "PNTS_PER_SCREEN" + - measurement_name: "FIRST_VALID_PNT" + - measurement_name: "LAST_VALID_PNT" + - measurement_name: "FIRST_POINT" + - measurement_name: "SPARSING_FACTOR" + - measurement_name: "SWEEPS_PER_ACQ" + - measurement_name: "POINTS_PER_PAIR" + - measurement_name: "PAIR_OFFSET" + - measurement_name: "VERTICAL_GAIN" + - measurement_name: "VERTICAL_OFFSET" + - measurement_name: "MAX_VALUE" + - measurement_name: "MIN_VALUE" + - measurement_name: "NOMINAL_BITS" + - measurement_name: "HORIZ_INTERVAL" + - measurement_name: "HORIZ_OFFSET" + - measurement_name: "PIXEL_OFFSET" + - measurement_name: "HORIZ_UNCERTAINTY" + - measurement_name: "ACQ_DURATION" +``` + +### Python code example +```Python +l1 = config.get(l1) +l1.device.get_tdiv() +#Returns timebase setting + +l1.device.set_waveformFormat('WORD') + +l1.device.get_waveform('C1') +#Returns a tuple with (header, wave array count, waveform) + +scan = amesh(robz, 0, 2, 2, roby, 0, 2, 2, 0.1,l1) +``` + +## References + +* User Manual: http://cdn.teledynelecroy.com/files/manuals/wm-rcm-e_rev_d.pdf +* BCU wiki page: http://wikiserv.esrf.fr/bliss/index.php/Lecroy diff --git a/doc/mkdocs.yml b/doc/mkdocs.yml index f7322a6252..97dba9a6b7 100755 --- a/doc/mkdocs.yml +++ b/doc/mkdocs.yml @@ -58,6 +58,7 @@ nav: - 428: config_keithley_428.md - Keithley 3706: config_keithley_3706.md - Keller: config_keller.md + - Lecroy: config_lecroy.md - Machine info: config_machine.md - MCA: - General: config_mca.md diff --git a/tests/test_configuration/tektronix.yml b/tests/test_configuration/tektronix.yml index dd4f069751..6d4f6ade8f 100644 --- a/tests/test_configuration/tektronix.yml +++ b/tests/test_configuration/tektronix.yml @@ -1,5 +1,5 @@ - class: TektronixOsc - module: tektronix + module: oscilloscope.tektronix plugin: bliss name: tektro2 vxi11: -- GitLab