diff --git a/Trees/IpfnDevices/DEMOADC1.py b/Trees/IpfnDevices/DEMOADC1.py new file mode 100755 index 0000000..22dd3b5 --- /dev/null +++ b/Trees/IpfnDevices/DEMOADC1.py @@ -0,0 +1,285 @@ +from MDSplus import Device, Data, Range, Dimension, Window, Signal, Int16Array +from ctypes import CDLL, c_char_p, c_short, byref, c_int + + +class DEMOADC1(Device): + """A Demo 4 Channel, 16 but digitizer""" +# This class inherits from the generic Device class +# Class Device provides the basic methods required by MDSplus, in particular the Add metod +# called when a new instance of this device has to be created in a tree in editing +# it is therefore only necessary to add here the device specific methods such as init and store +# It is however necessary to define the structure of the device, so that the superclass' Add method can +# build the appropiate device subtree. This information is specified as a list of dictionaries +# every element of the list specifies a node of the subtree associated with the device. The mandatory dictionary items +# for each node are: +# - path : the path name relative to the subtree root of the node +# - type : the type (usage) of the node, which can be either 'text', 'numeric', 'signal', 'action', 'structure' +# optional dictionary items formeach node are: +# - value : initial value forn that node +# - valueExpr : initial value when specified as an expression +# - options : a (list of) NCI options for the tree node, such as 'no_write_model', 'no_write_shot', 'compress_on_put' +# field parts will contain the list of dictionaries, and will be used by Device superclass. +# +# in the following methods, defice fields are referred by the syntax: self., where field_name is derived by the +# path of the corresponding tree node relative to the subtree (as specified by the corresponding path dictionary item), where #letters are lowercase and the dots and colons are replaced by underscores (except the first one). +# For example, tree node :ADDR is accessed by field self.addr and .CHANNEL_1:DATA by field self.channel_1_data +# All there firlds are TreeNode instances and therefore all TreeNode methods such as Data() or putData() can be used. + + parts = [ + {'path': ':ADDR', 'type': 'text'}, { + 'path': ':COMMENT', 'type': 'text'}, + {'path': ':CLOCK_FREQ', 'type': 'numeric', 'value': 10000}, + {'path': ':TRIG_SOURCE', 'type': 'numeric', 'value': 0}, + {'path': ':PTS', 'type': 'numeric', 'value': 1000}, + ] + for i in range(4): + parts.extend([ + {'path': '.CHANNEL_%d' % (i), 'type': 'structure'}, + {'path': '.CHANNEL_%d:START_IDX' % ( + i), 'type': 'numeric', 'value': 0}, + {'path': '.CHANNEL_%d:END_IDX' % ( + i), 'type': 'numeric', 'value': 1000}, + {'path': '.CHANNEL_%d:DATA' % (i), 'type': 'signal', 'options': ( + 'no_write_model', 'compress_on_put')}, + ]) + parts.extend([ + {'path': ':INIT_ACTION', 'type': 'action', + 'valueExpr': "Action(Dispatch('CAMAC_SERVER','INIT',50,None),Method(None,'init',head))", + 'options': ('no_write_shot',)}, + {'path': ':STORE_ACTION', 'type': 'action', + 'valueExpr': "Action(Dispatch('CAMAC_SERVER','STORE',50,None),Method(None,'store',head))", + 'options': ('no_write_shot',)}, + ]) + del(i) + +######################################INIT################################################################# +# init method, called to configure the ADC device. It will read configuration from the corresponding subtree +# and it will download configuration to the device + def init(self): + # Need to import some classes from MDSplus package + # The device will be configured via a shared library (libDemoAdc in the MDSplus distribution) defining the following routines: + # initialize(char *addr, int clockFreq, int postTriggerSamples) + # where clockFreq can have the following values: + # - 1 -> clock freq. = 1KHz + # - 2 -> clock freq. = 5KHz + # - 3 -> clock freq. = 10KHz + # - 4 -> clock freq. = 50 KHz + # - 5 -> clock freq. = 100KHz + # trigger(char *addr) + # acquire(char *addr, short *c1, short *c2 short *c3, short *c4) + # the routine acquire returns 4 simulated sinusoidal waveform signals at the following frequencies: + # Channel 1: 10Hz + # Channel 2: 50 Hz + # Channel 3: 100 Hz + # Channel 4: 200Hz + # + # The addr argument passed to all device routines is not used and simulates the device identifier + # used in real devices + # + # Since we need to link against a shaed library from python, we need ctypes package. + from ctypes import CDLL, c_int, c_char_p + try: + deviceLibCDLL = CDLL("libDemoAdcShr.so") + except: + print ('Cannot link to device library') + return 0 + +# deviceLib is the ctype DLL object which allows to call library routines + + +# in the following, we'll get data items in two steps (on the same line): +# 1) instantiate a TreeNode object, passing the integer nid to the constructor +# 2) read and evaluate (in the case the content is an expression) its content via TreeNode.data() method +# all data access operation will be but in a try block in order to check for missing or wrong configuration data + try: + address = self.addr.data() +# we expect to get a string in addr + except: + print ('Missing addr in device') + return 0 + +# read the clock frequency and convert to clock mode. We use a dictionary for the conversion, and assume + clockDict = {1000: 1, 5000: 2, 10000: 3, 50000: 4, 100000: 5} + try: + clockFreq = self.clock_freq.data() + clockMode = clockDict[clockFreq] + except: + print ('Missing or invalid clock frequency') + return 0 + +# read Post Trigger Samples and check for consistency + try: + pts = self.pts.data() + except: + print ('Missing or invalid Post Trigger Samples') + return 0 + +# all required configuation collected. Call external routine initialize passing the right parameters +# we use ctypes functions to convert python variable to appropriate C types to be passed to the external routine +# try: + print(address) + deviceLibCDLL.initialize( + c_char_p(address.encode()), c_int(clockMode), c_int(pts)) + #try: + # deviceLibCDLL.initialize( + # c_char_p(address), c_int(clockMode), c_int(pts)) + #except: + # print('Error initializing driver') + # return 0 +# return success + return 1 + + +##################################STORE################################################ +# store method, called to get samples from the ADC and to store waveforms in the tree + def store(self): + # import required symbols from MDSSplus and ctypes packages + + # instantiate library object + try: + deviceLib = CDLL("libDemoAdcShr.so") + except: + print ('Cannot link to device library') + return 0 + + +# get addr + try: + addr = self.addr.data() +# we expect to get a string in addr + except: + print ('Missing Addr in device') + return 0 + + +# instantiate four short arrays with 65536 samples each. They will be passed to the acquire() external routine + DataArray = c_short * 65536 + rawChan = [] + rawChan.append(DataArray()) + rawChan.append(DataArray()) + rawChan.append(DataArray()) + rawChan.append(DataArray()) + + status = deviceLib.acquire(c_char_p(addr.encode()), byref(rawChan[0]), byref( + rawChan[1]), byref(rawChan[2]), byref(rawChan[3])) + if status == -1: + print ('Acquisition Failed') + return 0 + +# at this point the raw signals are contained in rawChan1-4. We must now: +# 1) reduce the dimension of the stored array using the start idx and end idx parameters for each channel, which define +# the number of samples around the trigger which need to be stored in the pulse file (for this purpose the value of +# post trigger samples is also required) +# 2) build the appropriate timing information +# 3) Put all together in a Signal object +# 4) store the Signal object in the tree + +# read PostTriggerSamples + try: + pts = self.pts.data() + except: + print ('Missing or invalid Post Trigger Samples') + return 0 +# for each channel we read start idx and end idx + startIdx = [] + endIdx = [] + try: + for chan in range(0, 4): + currStartIdx = self.__getattr__( + 'channel_%d_start_idx' % (chan)).data() + currEndIdx = self.__getattr__( + 'channel_%d_end_idx' % (chan)).data() + startIdx.append(currStartIdx) + endIdx.append(currEndIdx) + except: + print ('Cannot read start idx or end idx') + return 0 +# 1)Build reduced arrays based on start idx and end idx for each channel +# recall that a transient recorder stores acquired data in a circular buffer and stops after acquiring +# PTS samples after the trigger. This means that the sample corresponding to the trigger is at offset PTS samples +# before the end of the acquired sample array. + +# the total number of samples returned by routine acquire() + totSamples = 65536 + +# we read the time associated with the trigger. It is specified in the TRIG_SOURCE field of the device tree structure. +# it will be required in order to associate the correct time with each acquired sample + try: + trigTime = self.trig_source.data() + except: + print ('Missing or invalid Post Trigger Samples') + return 0 +# we need clock frequency as well + try: + clockFreq = self.clock_freq.data() + clockPeriod = 1./clockFreq + except: + print ('Missing or invalid clock frequency') + return 0 + + +# the following steps are performed for each acquired channel + for chan in range(0, 4): + # first index of the part of interest of the sample array + actStartIdx = totSamples - pts + startIdx[chan] + # last index of the part of interest of the sample array + actEndIdx = totSamples - pts + endIdx[chan] +# make sure we do not exceed original array limits + if actStartIdx < 0: + actStartIdx = 0 + if actEndIdx > totSamples: + actEndIdx = totSamples - 1 +# build reshaped array + reducedRawChan = rawChan[chan][actStartIdx:actEndIdx] + + +# 2)Build timing information. For this purpose we use a MDSplus "Dimension" object which contains two fields: +# "Window" and "Axis". Window object defines the start and end index of the associated data array and the time which is +# associated with the sample at index 0. Several possible combination of start and end indexes are possible (the can also be +# negative numbers). We adopt here the following convention: consider index 0 as the index of the sample corresponding +# to the trigger, and therefore associated with the trigger time. From the way we have built the reduced raw sample array, +# it turns out that the start idx and end idx defined +# in the Window object are the same of the start and end indexes defined in the device configuration. +# +# The "Range" object describes a (possibly multispeed or busrt) clock. Its fields specify the clock period, the start and end time +# for that clock frequency. In our case we need to describe a continuous single speed clock, so there is no need to +# specify start and end times(it is a continuous, single speed clock). +# +# build the Dimension object in a single call + dim = Dimension(Window(startIdx[chan], endIdx[chan], trigTime), Range( + None, None, clockPeriod)) + + +# 3) Put all togenther in a "Signal" object. MDSplus Signal objects define three fields: samples, raw samples, dimension +# raw samples are contained in reducedRawChan. The computation required to convert the raw 16 bit sample into a +-10V +# value is: sample = 10.*rawSample/32768. We may compute a new float array containing such data and store it together +# with the raw sample (in the case we would like to reain also raw data. There is however a better way to do it +# by storing only the required information, i.e. the raw(16 bit) samples and the definition of the expression which +# converts raw data into actual voltage levels. Therefore, the first field of the Signal object will contain only the +# definition of an expression, which refers to the raw samples (the second field) of the same Signal object. +# The MDSplus syntax for this conversion is: 10.*$VALUE/32768. +# We shall use Data method compile() to build the MDSplus internal representation of this expression, and the stick it +# as the first field of the Signal object + convExpr = Data.compile("10.* $VALUE/32768.") + +# use MDSplus Int16Array object to vest the short array reducedRawChan into the appropriate MDSplus type + rawMdsData = Int16Array(reducedRawChan) + +# every MDSplus data type can have units associated with it + rawMdsData.setUnits("Count") + convExpr.setUnits("Volt") + +# build the signal object + signal = Signal(convExpr, rawMdsData, dim) + +# write the signal in the tree + try: + self.__getattr__('channel_%d_data' % (chan)).putData(signal) + except: + print ('Cannot write Signal in the tree') + return 0 + +# endfor chan in range(0,4): + +# return success (odd numbers in MDSplus) + return 1