diff --git a/@MyInstrument/MyInstrument.m b/@MyInstrument/MyInstrument.m index 0f82024..182b667 100644 --- a/@MyInstrument/MyInstrument.m +++ b/@MyInstrument/MyInstrument.m @@ -1,498 +1,498 @@ -classdef MyInstrument < dynamicprops & MyParsedInputs +classdef MyInstrument < dynamicprops & MyInputHandler properties (Access=public) name=''; interface=''; address=''; visa_brand='ni'; end properties (SetAccess=protected, GetAccess=public) %Contains the device object. struct() is a dummy, as Device %needs to always support properties for consistency. Device=struct(); %Contains a list of the commands available for the instrument as %well as the default values and input requirements CommandList=struct(); %Parses commands using an inputParser object CommandParser; %Trace object for storing data Trace=MyTrace(); end properties (Constant=true) % Default parameters for VISA connection DEFAULT_INP_BUFF_SIZE = 1e7; % buffer size bytes DEFAULT_OUT_BUFF_SIZE = 1e7; % buffer size bytes DEFAULT_TIMEOUT = 10; % Timeout in s end properties (Dependent=true) command_names; command_no; write_commands; read_commands; end events NewData; end methods (Access=protected) % This function is overloaded to add more parameters to the parser function p = createConstructionParser(this) p=inputParser(); % Ignore unmatched parameters p.KeepUnmatched = true; addRequired(p,'interface',@ischar); addRequired(p,'address',@ischar); addParameter(p,'name','',@ischar); addParameter(p,'visa_brand',this.visa_brand,@ischar); this.ConstructionParser=p; end end methods (Access=public) function this=MyInstrument(interface, address, varargin) createConstructionParser(this); %Loads parsed variables into class properties parseClassInputs(this,interface,address,varargin{:}) end function delete(this) %Closes the connection to the device closeDevice(this); %Deletes the device object try delete(this.Device); catch warning('Device object cannot be deleted') end end %% Read and write commands %Writes properties to device. Can take multiple inputs. With the %option all, the function writes default to all the %available writeable parameters. function writeProperty(this, varargin) %Parses the inputs using the CommandParser parse(this.CommandParser, varargin{:}); if this.CommandParser.Results.all % If the 'all' is true, write all the commands exec=this.write_commands; else % Check which commands were passed values ind_val=cellfun(@(x)... (~ismember(x, this.CommandParser.UsingDefaults)),... this.write_commands); exec=this.write_commands(ind_val); end for i=1:length(exec) %Creates the write command using the right string spec write_command=[this.CommandList.(exec{i}).command,... ' ',this.CommandList.(exec{i}).str_spec]; %Gets the value to write to the device this.(exec{i})=this.CommandParser.Results.(exec{i}); command=sprintf(write_command, this.(exec{i})); %Sends command to device fprintf(this.Device, command); end end % Wrapper for writeProperty that opens and closes the device function writePropertyHedged(this, varargin) openDevice(this); try writeProperty(this, varargin{:}); catch warning('Error while writing the properties:'); disp(varargin); end readProperty(this, 'all'); closeDevice(this); end function result=readProperty(this, varargin) result = struct(); read_all_flag = any(strcmp('all',varargin)); if read_all_flag % Read all the commands with read access exec=this.read_commands; else ind_r=ismember(varargin,this.read_commands); exec=varargin(ind_r); if any(~ind_r) % Issue warnings for commands not in the command_names warning('The following are not valid read commands:'); disp(varargin(~ind_r)); end end % concatenate all commands in one string read_command=join(cellfun(... @(cmd)this.CommandList.(cmd).command,exec,... 'UniformOutput',false),'?;:'); read_command=[read_command{1},'?;']; res_str = query(this.Device,read_command); % drop the end-of-the-string symbol and split res_str = split(res_str(1:end-1),';'); if length(exec)==length(res_str) for i=1:length(exec) result.(exec{i})=sscanf(res_str{i},... this.CommandList.(exec{i}).str_spec); %Assign the values to the MyInstrument properties this.(exec{i})=result.(exec{i}); end else warning(['Not all the properties could be read, ',... 'no instrument class values are not updated']); end end % Wrapper for readProperty that opens and closes the device function result=readPropertyHedged(this, varargin) openDevice(this); try result = readProperty(this, varargin{:}); catch warning('Error while reading the properties:'); disp(varargin); end closeDevice(this); end %Triggers event for acquired data function triggerNewData(this) notify(this,'NewData') end function HdrStruct=readHeader(this) Values=readPropertyHedged(this,'all'); for i=1:length(this.read_commands) HdrStruct.(this.read_commands{i}).value=... Values.(this.read_commands{i}); HdrStruct.(this.read_commands{i}).str_spec=... this.CommandList.(this.read_commands{i}).str_spec; end end %% Processing of the class variable values % Extend the property value based on val_list function std_val = standardizeValue(this, cmd, varargin) if ~ismember(cmd,this.command_names) warning('%s is not a valid command',cmd); std_val = ''; return end vlist = this.CommandList.(cmd).val_list; % The value to normalize can be explicitly passed as % varargin{1}, otherwise use this.cmd as value if isempty(varargin) val = this.(cmd); else val = varargin{1}; end % find matching commands ismatch = false(1,length(vlist)); for i=1:length(vlist) n = min([length(val), length(vlist{i})]); % compare first n symbols disregarding case ismatch(i) = strncmpi(val, vlist{i},n); end % out of matching names pick the longest if any(ismatch) mvlist = vlist(ismatch); %Finds the length of each element of mvlist n_el=cellfun(@(x) length(x), mvlist); %Sets std_val to the longest element std_val=mvlist{n_el==max(n_el)}; % sets the property if value was not given explicitly if isempty(varargin) this.(cmd) = std_val; end else warning(['The value %s is not in the val_list ',... 'of %s command'], val, cmd) std_val = val; end end % Return the list of long command values excluding abbreviations function std_val_list = stdValueList(this, cmd) if ~ismember(cmd,this.command_names) warning('%s is not a valid command',cmd); std_val_list = {}; return end vlist = this.CommandList.(cmd).val_list; % Select the commands, which appear only once in the beginnings % of the strings in val_list long_val_ind = cellfun(... @(x)(sum(startsWith(vlist,x,'IgnoreCase',true))==1),vlist); std_val_list = vlist(long_val_ind); end %% Connect, open, configure and close the device % Connects to the device function connectDevice(this, interface, address) try % visa brand, 'ni' by default vb = this.visa_brand; switch lower(interface) case 'instr_list' % load the InstrumentList structure InstrumentList = getLocalSettings('InstrumentList'); % In this case 'address' is the instrument name in % the list instr_name = address; if ~isfield(InstrumentList, instr_name) error('%s is not a field of InstrumentList',... instr_name) end % A check to prevent hypothetical endless recursion if isequal(InstrumentList.(instr_name).interface,'instr_list') error('') end % Connect using the loaded parameters connectDevice(this,... InstrumentList.(instr_name).interface,... InstrumentList.(instr_name).address); % Assign name automatically, but not overwrite if % already specified if isempty(this.name) this.name = instr_name; end case 'constructor' % in this case the 'address' is a command % (ObjectConstructorName) as returned by the % instrhwinfo this.Device=eval(address); case 'visa' this.Device=visa(vb, address); case 'tcpip' this.Device=visa(vb, sprintf(... 'TCPIP0::%s::inst0::INSTR',address)); case 'usb' this.Device=visa(vb, sprintf(... 'USB0::%s::INSTR',address)); case 'serial' com_no = sscanf(address,'COM%i'); this.Device = visa(vb, sprintf(... 'ASRL%i::INSTR',com_no)); otherwise warning('Device is not connected: unknown interface'); end configureDeviceDefault(this); catch warning('Device is not connected'); end end % Opens the device if it is not open function openDevice(this) if ~isopen(this) try fopen(this.Device); catch % try to find and close all the devices with the same % VISA resource name try instr_list=instrfind('RsrcName',this.Device.RsrcName); fclose(instr_list); fopen(this.Device); warning('Multiple instrument objects of address %s exist',... this.address); catch error('Could not open device') end end end end % Closes the connection to the device function closeDevice(this) if isopen(this) fclose(this.Device); end end function configureDeviceDefault(this) dev_prop_list = properties(this.Device); if ismember('OutputBufferSize',dev_prop_list) this.Device.OutputBufferSize = this.DEFAULT_OUT_BUFF_SIZE; end if ismember('InputBufferSize',dev_prop_list) this.Device.InputBufferSize = this.DEFAULT_INP_BUFF_SIZE; end if ismember('Timeout',dev_prop_list) this.Device.Timeout = this.DEFAULT_TIMEOUT; end end %Checks if the connection to the device is open function bool=isopen(this) try bool=strcmp(this.Device.Status, 'open'); catch warning('Cannot access device Status property'); bool=false; end end %% addCommand %Adds a command to the CommandList function addCommand(this, tag, command, varargin) p=inputParser(); addRequired(p,'tag',@ischar); addRequired(p,'command',@ischar); addParameter(p,'default','placeholder'); addParameter(p,'classes',{},@iscell); addParameter(p,'attributes',{},@iscell); addParameter(p,'str_spec','%e',@ischar); % list of the values the variable can take, {} means no % restriction addParameter(p,'val_list',{},@iscell); addParameter(p,'access','rw',@ischar); parse(p,tag,command,varargin{:}); %Adds the command to be sent to the device this.CommandList.(tag).command=command; this.CommandList.(tag).access=p.Results.access; this.CommandList.(tag).write_flag=contains(p.Results.access,'w'); this.CommandList.(tag).read_flag=contains(p.Results.access,'r'); this.CommandList.(tag).default=p.Results.default; this.CommandList.(tag).val_list=p.Results.val_list; % Adds the string specifier to the list. if the format % specifier is not given explicitly, try to infer if ismember('str_spec', p.UsingDefaults) this.CommandList.(tag).str_spec=... formatSpecFromAttributes(this,p.Results.classes... ,p.Results.attributes); elseif strcmp(p.Results.str_spec,'%b') % b is a non-system specifier to represent the % logical type this.CommandList.(tag).str_spec='%i'; else this.CommandList.(tag).str_spec=p.Results.str_spec; end % Adds the attributes for the input to the command. If not % given explicitly, infer from the format specifier if ismember('classes',p.UsingDefaults) [this.CommandList.(tag).classes,... this.CommandList.(tag).attributes]=... attributesFromFormatSpec(this, p.Results.str_spec); else this.CommandList.(tag).classes=p.Results.classes; this.CommandList.(tag).attributes=p.Results.attributes; end % Adds a property to the class corresponding to the tag if ~isprop(this,tag) addprop(this,tag); end this.(tag)=p.Results.default; end %Creates inputParser using the command list function p = createCommandParser(this) %Use input parser %Requires input of the appropriate class p=inputParser; p.StructExpand=0; %Flag for whether the command should initialize the device with %defaults addParameter(p, 'all',false,@islogical); for i=1:length(this.write_commands) %Adds optional inputs for each command, with the %appropriate default value from the command list and the %required attributes for the command input. tag=this.write_commands{i}; % Create validation function based on properties: % class, attributes and list of values if ~isempty(this.CommandList.(tag).val_list) if all(cellfun(@ischar, this.CommandList.(tag).val_list)) % for textual values use case insentice string comparison v_func = @(x) any(cellfun(@(y) strcmpi(y, x),... this.CommandList.(tag).val_list)); else % for everything else compare as it is v_func = @(x) any(cellfun(@(y) isequal(y, x),... this.CommandList.(tag).val_list)); end else v_func = @(x) validateattributes(x,... this.CommandList.(tag).classes,... this.CommandList.(tag).attributes); end addParameter(p, tag,... this.CommandList.(tag).default, v_func); end this.CommandParser=p; end %% Auxiliary functions for auto format assignment to commands function str_spec=formatSpecFromAttributes(~,classes,attributes) if ismember('char',classes) str_spec='%s'; elseif ismember('logical',classes)||... (ismember('numeric',classes)&&... ismember('integer',attributes)) str_spec='%i'; else %assign default value, i.e. double str_spec='%e'; end end function [class,attribute]=attributesFromFormatSpec(~, str_spec) % find index of the first letter after the % sign ind_p=strfind(str_spec,'%'); ind=ind_p+find(isletter(str_spec(ind_p:end)),1)-1; str_spec_letter=str_spec(ind); switch str_spec_letter case {'d','f','e','g'} class={'numeric'}; attribute={}; case 'i' class={'numeric'}; attribute={'integer'}; case 's' class={'char'}; attribute={}; case 'b' class={'logical'}; attribute={}; otherwise % Any of the above classes will pass class={'numeric','char','logical'}; attribute={}; end end end %% Get functions methods function command_names=get.command_names(this) command_names=fieldnames(this.CommandList); end function write_commands=get.write_commands(this) ind_w=structfun(@(x) x.write_flag, this.CommandList); write_commands=this.command_names(ind_w); end function read_commands=get.read_commands(this) ind_r=structfun(@(x) x.read_flag, this.CommandList); read_commands=this.command_names(ind_r); end function command_no=get.command_no(this) command_no=length(this.command_names); end end end \ No newline at end of file diff --git a/@MyLakeshore336/MyLakeshore336.m b/@MyLakeshore336/MyLakeshore336.m new file mode 100644 index 0000000..c2450ad --- /dev/null +++ b/@MyLakeshore336/MyLakeshore336.m @@ -0,0 +1,25 @@ +% Class communication with Lakeshore Model 336 temperature controller. +% Tested with DPO4034, DPO3034 +classdef MyLakeshore336 < MyInstrument + + properties + Property1 + end + + methods (Access=private) + function this=MyLakeshore336(interface, address, varargin) + this@MyInstrument(interface, address, varargin{:}); + + createCommandList(this); + createCommandParser(this); + connectDevice(this, interface, address); + end + + function createCommandList(this) + % channel from which the data is transferred + addCommand(this,'channel','DATa:SOUrce','default',1,... + 'str_spec','CH%i'); + end + end +end + diff --git a/@MyLogger/MyLogger.m b/@MyLogger/MyLogger.m index c7ba709..2853fd2 100644 --- a/@MyLogger/MyLogger.m +++ b/@MyLogger/MyLogger.m @@ -1,175 +1,175 @@ % Generic logger that executes MeasFcn according to MeasTimer, stores the % results and optionally continuously saves them. MeasFcn should be a % function with no arguments. Saving functionality works properly if % MeasFcn returns a number or array of numbers, while intrinsically the % logger can store any kind of outputs. -classdef MyLogger < handle +classdef MyLogger < handle & MyInputHandler properties (Access=public) MeasTimer = []; % Timer object MeasFcn = @()0; save_cont = false; save_file = ''; data_headers = {}; % Cell array of column headers % format specifiers for data saving and display time_fmt = '%14.3f'; % Save time as posixtime up to ms precision data_field_width = '24'; data_fmt = '%24.14e'; % Save data as reals with 14 decimal digits % Format for displaying last reading label: value disp_fmt = '%15s: %.2e'; end properties (SetAccess=protected, GetAccess=public) % Trace = MyTrace(); % Trace object for communication with Daq timestamps = []; % Times at which data was aqcuired data = []; % Stored cell array of measurements last_meas_stat = 2; % If last measurement was succesful % 0-false, 1-true, 2-never measured end events NewData; end methods function this = MyLogger(varargin) - p=inputParser(); - % Ignore unmatched parameters + createConstructionParser(this); p.KeepUnmatched = true; - parseClassInputs(p, this, varargin{:}); + addClassProperties(this.ConstructionParser); + parseClassInputs(this, varargin{:}); if ismember('MeasTimer', p.UsingDefaults) % Create and confitugure timer unless it was supplied % externally in varargin this.MeasTimer = timer(); this.MeasTimer.BusyMode = 'queue'; this.MeasTimer.ExecutionMode = 'FixedRate'; this.MeasTimer.TimerFcn = @(~,event)LoggerFcn(this,event); end end function delete(this) %stop and delete the timer stop(this.MeasTimer); delete(this.MeasTimer); end function LoggerFcn(this, event) time = datetime(event.Data.time); try meas_result = this.MeasFcn(); % append measurement result together with time stamp this.timestamps=[this.timestamps; time]; this.data=[this.data; {meas_result}]; this.last_meas_stat=1; % last measurement ok catch warning(['Logger cannot take measurement at time = ',... datestr(time)]); this.last_meas_stat=0; % last measurement not ok end triggerNewData(this); % save the point to file if continuous saving is enabled and % last measurement was succesful if this.save_cont&&(this.last_meas_stat==1) try exstat = exist(this.save_file,'file'); if exstat==0 % if the file does not exist, create it and write % header names createFile(this.save_file); fid = fopen(this.save_file,'w'); writeColumnHeaders(this, fid); else % otherwise open for appending fid = fopen(this.save_file,'a'); end fprintf(fid, this.time_fmt, posixtime(time)); fprintf(fid, this.data_fmt, meas_result); fprintf(fid,'\r\n'); fclose(fid); catch warning(['Logger cannot save data at time = ',... datestr(time)]); % Try closing fid in case it is still open try fclose(fid); catch end end end end % save the entire data record function saveLog(this) try createFile(this.save_file); fid = fopen(this.save_file,'w'); writeColumnHeaders(this, fid); for i=1:length(this.timestamps) fprintf(fid, this.time_fmt,... posixtime(this.timestamps(i))); fprintf(fid, this.data_fmt,... this.data{i}); fprintf(fid,'\r\n'); end fclose(fid); catch warning('Data was not saved'); % Try closing fid in case it is still open try fclose(fid); catch end end end function clearLog(this) this.timestamps = []; this.data = []; end function writeColumnHeaders(this, fid) % write data headers to file if specified fprintf(fid, 'POSIX time [s]'); for i=1:length(this.data_headers) fprintf(fid, ['%',this.data_field_width,'s'],... this.data_headers{i}); end fprintf(fid,'\r\n'); end function start(this) start(this.MeasTimer); end function stop(this) stop(this.MeasTimer); end function str = dispLastReading(this) if isempty(this.timestamps) str = ''; else str = ['Last reading ',char(this.timestamps(end)),newline]; last_data = this.data{end}; for i=1:length(last_data) if length(this.data_headers)>=i lbl = this.data_headers{i}; else lbl = sprintf('data%i',i); end str = [str,... sprintf(this.disp_fmt,lbl,last_data(i)),newline]; end end end %Triggers event for acquired data function triggerNewData(this) notify(this,'NewData') end end end diff --git a/@MyTpg/MyTpg.m b/@MyTpg/MyTpg.m index 0463b6c..4898802 100644 --- a/@MyTpg/MyTpg.m +++ b/@MyTpg/MyTpg.m @@ -1,157 +1,157 @@ % Class for communication with Pfeiffer TPG single and dual pressure gauge % controllers. -% Tested with TPG 361 and TPG 362. +% Tested with TPG 362. classdef MyTpg < MyInstrument properties (Constant=true) % Named constants for communication ETX = char(3); % end of text CR = char(13); % carriage return LF = char(10); %#ok % line feed ENQ = char(5); % enquiry ACK = char(6); % acknowledge NAK = char(21); % negative acknowledge end properties (SetAccess=protected, GetAccess=public) pressure1 = 0; % numeric values of pressure pressure2 = 0; stat1; stat2; gauge_id1; gauge_id2; pressure_unit = ''; end properties (Dependent=true) pressure_str1; % display string with measurement unit pressure_str2; end methods (Access=public) function this = MyTpg(interface, address, varargin) this@MyInstrument(interface, address, varargin{:}); connectDevice(this, interface, address); end % read pressure from a single channel or both channels at a time function p_arr = readPressure(this) query(this.Device,['PRX',this.CR,this.LF]); str = query(this.Device,this.ENQ); % Extract pressure and gauge status from reading. arr = sscanf(str,'%i,%e,%i,%e'); p_arr=transpose(arr(2:2:end)); this.pressure1 = p_arr(1); this.pressure2 = p_arr(2); % Status codes: % 0 –> Measurement data okay % 1 –> Underrange % 2 –> Overrange % 3 –> Sensor error % 4 –> Sensor off (IKR, PKR, IMR, PBR) % 5 –> No sensor (output: 5,2.0000E-2 [hPa]) % 6 –> Identification error this.stat1 = gaugeStatusFromCode(this, arr(1)); this.stat2 = gaugeStatusFromCode(this, arr(3)); triggerNewData(this); end function pu = readPressureUnit(this) query(this.Device,['UNI',this.CR,this.LF]); str = query(this.Device,this.ENQ); % Pressure units correspondence table: % 0 –> mbar/bar % 1 –> Torr % 2 –> Pascal % 3 –> Micron % 4 –> hPascal (default) % 5 –> Volt pu_code = sscanf(str,'%i'); pu = pressureUnitFromCode(this, pu_code); this.pressure_unit = pu; end function id_list = readGaugeId(this) query(this.Device,['TID',this.CR,this.LF]); str = query(this.Device,this.ENQ); id_list = deblank(strsplit(str,{','})); this.gauge_id1 = id_list{1}; this.gauge_id2 = id_list{2}; end function p_arr = readAllHedged(this) openDevice(this); try p_arr = readPressure(this); readPressureUnit(this); readGaugeId(this); catch p_arr = [0,0]; warning('Error while communicating with gauge controller') end closeDevice(this) end function code_list = turnGauge(this) query(this.Device,['SEN',char(1,1),this.CR,this.LF]); str = query(this.Device,this.ENQ); code_list = deblank(strsplit(str,{','})); end % Convert numerical code for gauge status to a string function str = gaugeStatusFromCode(~, code) switch int8(code) case 0 str = 'Measurement data ok'; case 1 str = 'Underrange'; case 2 str = 'Overrange'; case 3 str = 'Sensor error'; case 4 str = 'Sensor off'; case 5 str = 'No sensor'; case 6 str = 'Identification error'; otherwise str = ''; warning('Unknown gauge status code %i', code); end end % Convert numerical code for pressure unit to a string function str = pressureUnitFromCode(~, code) switch int8(code) case 0 str = 'mbar'; case 1 str = 'Torr'; case 2 str = 'Pa'; case 3 str = 'Micron'; case 4 str = 'hPa'; case 5 str = 'Volt'; otherwise str = ''; warning('unknown pressure unit, code=%i',pu_num) end end end %% Get functions methods function p_str = get.pressure_str1(this) p_str = sprintf('%.2e %s', this.pressure1, this.pressure_unit); end function p_str = get.pressure_str2(this) p_str = sprintf('%.2e %s', this.pressure2, this.pressure_unit); end end end diff --git a/Utility functions/@MyParsedInputs/MyParsedInputs.m b/Utility functions/@MyInputHandler/MyInputHandler.m similarity index 98% rename from Utility functions/@MyParsedInputs/MyParsedInputs.m rename to Utility functions/@MyInputHandler/MyInputHandler.m index fd0e3dc..70c9144 100644 --- a/Utility functions/@MyParsedInputs/MyParsedInputs.m +++ b/Utility functions/@MyInputHandler/MyInputHandler.m @@ -1,61 +1,61 @@ -classdef MyParsedInputs < handle +classdef MyInputHandler < handle properties (SetAccess=protected, GetAccess=public) %Input parser for class constructor ConstructionParser; end methods (Access=protected) % Create parser. Can contain parameter additions % if overloaded in a subclass function p = createConstructionParser(this) p=inputParser(); this.ConstructionParser=p; end % Add all the properties the class which are not present in the % scheme of ConstructionParser and which have public set acces % to the scheme of ConstructionParser function addClassProperties(this) thisMetaclass = metaclass(this); for i=1:length(thisMetaclass.PropertyList) Tmp = thisMetaclass.PropertyList(i); % Constant, Dependent and Abstract propeties cannot be set if (~Tmp.Constant)&&(~Tmp.Abstract)&&(~Tmp.Dependent)&&... strcmpi(Tmp.SetAccess,'public')&&... (~ismember(Tmp.Name, p.Parameters)) if Tmp.HasDefault def = Tmp.DefaultValue; else def = []; end addParameter(this.ConstructionParser, Tmp.Name, def); end end end % parse varargin and assign results to class properties % with the same names as parameters function parseClassInputs(this, varargin) parse(this.ConstructionParser, varargin{:}); % assign results that have associated class properties with % public set access for i=1:length(this.ConstructionParser.Parameters) par = this.ConstructionParser.Parameters{i}; metaprop=findprop(this, par); if ~ismember(par, this.ConstructionParser.UsingDefaults)&&... isprop(this, par)&&... strcmpi(metaprop.SetAccess,'public') try this.(par) = this.ConstructionParser.Results.(par); catch warning(['Value of the input parameter ''',... par,''' could not be assigned to property']) end end end end end end