diff --git a/@MyLogger/MyLogger.m b/@MyLogger/MyLogger.m index 2a8fc46..4278268 100644 --- a/@MyLogger/MyLogger.m +++ b/@MyLogger/MyLogger.m @@ -1,144 +1,144 @@ % 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 properties (Access=public) MeasTimer = []; % Timer object MeasFcn = @()0; save_cont = false; save_file = ''; data_headers = {}; % Cell array of column headers 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 % format specifiers for data saving 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 end methods function this = MyLogger(varargin) p=inputParser(); % Ignore unmatched parameters p.KeepUnmatched = true; filt_varargin = parseClassInputs(p, this, varargin{:}); if ismember('MeasTimer', p.UsingDefaults) % Create and confitugure timer unless it was supplied % externally in varargin this.MeasTimer = timer(filt_varargin{:}); 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.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 % 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 end end diff --git a/GUIs/GuiLogger.mlapp b/GUIs/GuiLogger.mlapp index aa5e3a1..e4d633f 100644 Binary files a/GUIs/GuiLogger.mlapp and b/GUIs/GuiLogger.mlapp differ diff --git a/Utility functions/App utilities/linkControlElement.m b/Utility functions/App utilities/linkControlElement.m index bec0ccd..2e7d2e1 100644 --- a/Utility functions/App utilities/linkControlElement.m +++ b/Utility functions/App utilities/linkControlElement.m @@ -1,89 +1,91 @@ +% By using app.linked_elem_list, create a correspondence between a property +% of MyInstrument class (named prop_tag) and an element of the app gui function linkControlElement(app, elem, prop_tag, varargin) p=inputParser(); % GUI control element addRequired(p,'elem'); % Instrument command to be linked to the GUI element addRequired(p,'prop_tag',@ischar); % If input_presc is given, the value assigned to the instrument propery % is related to the value x displayed in GUI as x/input_presc. addParameter(p,'input_presc',1,@isnumeric); % Add an arbitrary function for processing the value, read from the % device before outputting it. addParameter(p,'out_proc_fcn',@(x)x,@(f)isa(f,'function_handle')); addParameter(p,'create_callback_fcn',@(x)0,@(f)isa(f,'function_handle')); % For drop-down menues initializes entries automatically based on the % list of values. Ignored for all the other control elements. addParameter(p,'init_val_list',false,@islogical); parse(p,elem,prop_tag,varargin{:}); % If the property is not present in the instrument class, disable the % control - if ~isfield(app.Instr.CommandList, prop_tag) + if isprop(app.Instr, prop_tag) elem.Enable='off'; elem.Visible='off'; return end % The property-control link is established by assigning the tag % and adding the control to the list of linked elements elem.Tag = prop_tag; app.linked_elem_list = [app.linked_elem_list, elem]; % If the create_callback_fcn is set, assign it to the % ValueChangedFcn which passes the field input to the instument if ~ismember('create_callback_fcn',p.UsingDefaults) elem.ValueChangedFcn = feval(p.Results.create_callback_fcn); end % If prescaler is given, add it to the element as a new property if p.Results.input_presc ~= 1 if isprop(elem, 'InputPrescaler') warning(['The InputPrescaler property already exists',... ' in the control element']); else addprop(elem,'InputPrescaler'); end elem.InputPrescaler = p.Results.input_presc; end % Add an arbitrary function for output processing if ~ismember('out_proc_fcn',p.UsingDefaults) if isprop(elem, 'OutputProcessingFcn') warning(['The OutputProcessingFcn property already exists',... ' in the control element']); else addprop(elem,'OutputProcessingFcn'); end elem.OutputProcessingFcn = p.Results.out_proc_fcn; end % Auto initialization of entries, for dropdown menus only if p.Results.init_val_list && isequal(elem.Type, 'uidropdown') try cmd_val_list = app.Instr.CommandList.(prop_tag).val_list; if all(cellfun(@ischar, cmd_val_list)) % If the command has only string values, get the list of % values ignoring abbreviations cmd_val_list = stdValueList(app.Instr, prop_tag); elem.Items = lower(cmd_val_list); elem.ItemsData = cmd_val_list; else % Items in a dropdown should be strings, so convert if % necessary str_list=cell(length(cmd_val_list), 1); for i=1:length(cmd_val_list) if ~ischar(cmd_val_list{i}) str_list{i}=num2str(cmd_val_list{i}); end end elem.Items = str_list; % Put raw values in ItemsData elem.ItemsData = cmd_val_list; end catch warning(['Could not automatically assign values',... - ' when linking the ',prop_tag,' property']); + ' when linking ',prop_tag,' property']); end end end diff --git a/Utility functions/App utilities/updateGui.m b/Utility functions/App utilities/updateGui.m index b4ecee1..b6d9ac6 100644 --- a/Utility functions/App utilities/updateGui.m +++ b/Utility functions/App utilities/updateGui.m @@ -1,33 +1,34 @@ % Set values for all the gui elements listed in app.linked_elem_list % according to the properties of an object (app.Instr by default) % Instrument property corresponds to the control element having the same % tag as property name. % If specified within the control element OutputProcessingFcn or % InputPrescaler is applied to the property value first function updateGui(app, varargin) if ~isempty(varargin) - Obj = varargin{1}; + SrcObj = varargin{1}; elseif isprop(app, 'Instr') % app.Instr is a MyInstrument object, default choice - Obj = app.Instr; + SrcObj = app.Instr; else - error('Cannot update gui'); + error('Source object is not provided for gui update'); end for i=1:length(app.linked_elem_list) tmpelem = app.linked_elem_list(i); - if isprop(Obj, tmpelem.Tag) - % update the element value - tmpval = Obj.(tmpelem.Tag); + try + % update the element value based on Obj.(tag), + % where tag can contain a reference to sub-objects + tmpval = getSubProperty(SrcObj, tmpelem.Tag); % scale the value if the control element has a prescaler if isprop(tmpelem, 'OutputProcessingFcn') tmpval = tmpelem.OutputProcessingFcn(tmpval); elseif isprop(tmpelem, 'InputPrescaler') tmpval = tmpval*tmpelem.InputPrescaler; end tmpelem.Value = tmpval; - else + catch end end end diff --git a/Utility functions/getSubProperty.m b/Utility functions/getSubProperty.m new file mode 100644 index 0000000..a33fcc0 --- /dev/null +++ b/Utility functions/getSubProperty.m @@ -0,0 +1,14 @@ +% Get value of object property named by tag, possibly supporting references +% to sub-objects (in this case tag is 'SubObj1.SubObj2.property'). +function val = getSubProperty(Obj, tag) + tag_parts = strsplit(tag,'.'); + val = Obj; + for i=1:length(tag_parts) + try + val = val.(tag_parts{i}); + catch + error('Object does not have %s property', tag_parts{i}) + end + end +end +