diff --git a/@MyGuiSync/MyGuiSync.m b/@MyGuiSync/MyGuiSync.m index c1d158f..eed028b 100644 --- a/@MyGuiSync/MyGuiSync.m +++ b/@MyGuiSync/MyGuiSync.m @@ -1,623 +1,622 @@ % A mechanism to implement synchronization between parameters and GUI % elements in app-based GUIs classdef MyGuiSync < handle properties (GetAccess = public, SetAccess = protected) - Listeners = struct() % Link structures Links = struct( ... 'GuiElement', {}, ... % graphics object 'gui_element_prop', {}, ... 'inputProcessingFcn', {}, ... % applied after a value is ... % inputed to GUI 'outputProcessingFcn', {}, ... % applied before a new value is ... % displayed in GUI 'getTargetFcn', {}, ... 'setTargetFcn', {}, ... 'Listener', {} ... % PostSet listener (optional) ); % List of objects to be deleted when App is deleted cleanup_list = {} end properties (Access = protected) App = [] updateGuiFcn createCallbackFcn end methods (Access = public) function this = MyGuiSync(App, varargin) p = inputParser(); addRequired(p, 'App', ... @(x)assert(isa(x, 'matlab.apps.AppBase'), ... 'App must be a Matlab app.')); % Deletion of kernel object triggers the delition of app addParameter(p, 'KernelObj', [], @(x)assert( ... ismember('ObjectBeingDeleted', events(x)), ... ['Object must define ''ObjectBeingDeleted'' to be an ' ... 'app kernel.'])); % Optional function, executed after an app parameter has been % updated (either externally of internally) addParameter(p, 'updateGuiFcn', [], ... @(x)isa(x, 'function_handle')); addParameter(p, 'createCallbackFcn', [], ... @(x)isa(x, 'function_handle')); parse(p, App, varargin{:}); this.updateGuiFcn = p.Results.updateGuiFcn; this.createCallbackFcn = p.Results.createCallbackFcn; this.App = App; this.Listeners.AppDeleted = addlistener(App, ... 'ObjectBeingDeleted', @(~, ~)delete(this)); if ~ismember('KernelObj', p.UsingDefaults) addToCleanup(this, KernelObj); this.Listeners.KernelObjDeleted = addlistener(KernelObj,... 'ObjectBeingDeleted', @this.kernelDeletedCallback); end end function delete(this) % Delete general listeners try lnames=fieldnames(this.Listeners); for i=1:length(lnames) try delete(this.Listeners.(lnames{i})); catch fprintf(['Could not delete the listener to ' ... '''%s'' event.\n'], lnames{i}) end end catch fprintf('Could not delete listeners.\n'); end % Delete link listeners for i=1:length(this.Links) try delete(this.Links(i).Listener); catch end end % Delete the content of cleanup list for i = 1:length(this.cleanup_list) Obj = this.cleanup_list{i}; try % Check if the object has an appropriate delete method. % This is a safety measure to never delete a file by % accident. if ismethod(Obj, 'delete') delete(Obj); else fprintf(['Object of class ''%s'' ' ... 'does not have ''delete'' method.\n'], ... class(Obj)) end catch fprintf(['Could not delete an object of class ' ... '''%s'' from the cleanup list.\n'], class(Obj)) end end end % Establish a correspondence between the value of a GUI element and % some other property of the app % % Elem - graphics object % prop_tag - reference to a content of app function addLink(this, Elem, prop_ref, varargin) % Parse function inputs p = inputParser(); p.KeepUnmatched = true; % The decision whether to create ValueChangedFcn and % a PostSet callback is made automatically by this function, % but the parameters below enforce these functions to be *not* % created. addParameter(p, 'create_value_changed_fcn', true, @islogical); addParameter(p, 'event_update', true, @islogical); parse(p, Elem, prop_tag, varargin{:}); % Make the list of unmatched name-value pairs for subroutine sub_varargin = struct2namevalue(p.Unmatched); % Find the handle object which the end property belongs to, % the end property name and, possibly, further subscripts [Hobj, hobj_prop, RelSubs] = parseReference(this, prop_ref); % Check if the specified target is accessible for reading try if isempty(RelSubs) Hobj.(hobj_prop); else subsref(Hobj.(hobj_prop), RelSubs); end catch disp(['Property referenced by ' prop_ref ... ' is not accessible, the corresponding GUI ' ... 'element will be not linked and will be disabled.']) Elem.Enable = 'off'; return end % Create the basis of link structure (everything except for % set/get functions) Link = makeLinkBase(this, Elem, prop_ref, sub_varargin{:}); % Do additional link processing in the case of % MyInstrument commands if isa(Hobj, 'MyInstrument') && ... ismember(hobj_prop, Hobj.command_names) Link = extendMyInstrumentLink(this, Link, Hobj, hobj_prop); end % Assign the function that returns the value of reference Link.getTargetFcn = createGetTargetFcn(this, Hobj, ... hobj_prop, RelSubs); % Check if ValueChanged callback needs to be created create_vcf = p.Results.create_value_changed_fcn && ... checkCreateVcf(this, Elem, elem_prop, Hobj, hobj_prop); if create_vcf % A public CreateCallback method needs to intorduced in the % app, as officially Matlab apps do not support external % callback assignment (as of the version of Matlab 2019a) assert(~isempty(this.createCallbackFcn), ... ['Matlab app must define a public wrapper for ' ... 'createCallbackFcn in order for GuiSync to be able to ' ... 'automatically assign ValueChanged callbacks. ' ... 'The wrapper method must have signature ' ... 'publicCreateCallbackFcn(app, callbackFunction).']); % Assign the function that sets new value to reference Link.setTargetFcn = createSetTargetFcn(this, Hobj, ... hobj_prop, RelSubs); Elem.ValueChangedFcn = createValueChangedCallback(this, ... LinkStruct); end % Attempt creating a callback to PostSet event for the target % property. If such callback is not created, the link needs to % be updated manually. if p.Results.event_update try Link.Listener = addlistener(Hobj, hobj_prop, ... 'PostSet', createPostSetCallback(this, LinkStruct)); catch end end % Update the value of GUI element updateLinkedElement(this, Link); % Store the link structure this.Links(end+1) = Link; end function reLink(this, Elem, prop_ref) % Find the link structure corresponding to Elem ind = find(arrayfun( @(x)isequal(x.GuiElement, Elem), ... this.Links)); assert(length(ind) == 1, ['No or multiple existing links ' ... 'found during a relinking attempt.']) % Delete and clear the existing listener if ~isempty(this.Links(ind).Listener) delete(this.Links(ind).Listener); this.Links(ind).Listener = []; end [Hobj, hobj_prop, RelSubs] = parseReference(this, prop_ref); this.Links(ind).getTargetFcn = createGetTargetFcn(this, ... Hobj, hobj_prop, RelSubs); if ~isempty(this.Links(ind).setTargetFcn) % Create a new ValueChanged callback this.Links(ind).setTargetFcn = createSetTargetFcn(this, ... Hobj, hobj_prop, RelSubs); this.Links(ind).Elem.ValueChangedFcn = ... createValueChangedCallback(this, LinkStruct); end % Attempt creating a new listener try this.Links(ind).Listener = addlistener(Hobj, hobj_prop, ... 'PostSet', createPostSetCallback(this, LinkStruct)); catch end % Update the value of GUI element according to the new % reference updateLinkedElement(this, this.Links(ind)); end function updateLinkedElements(this) for i=1:length(this.Links) % Elements updated by callbacks should not be updated % manually if isempty(this.Links(i).Listener) updateLinkedElement(this, this.Links(i)); end end % Optionally execute the update function defined within % the App if ~isempty(this.updateGuiFcn) this.updateGuiFcn(); end end % Update the value of one linked GUI element. % Arg2 can be a link structure or a GUI element for which the % corresponding link structure needs to be found. function updateLinkedElement(this, Arg2) if isstruct(Arg2) Link = Arg2; val = Link.getTargetFcn(); if ~isempty(Link.outputProcessingFcn) val = Link.outputProcessingFcn(val); end % Setting value to a matlab app elemen is time consuming, % so first check if the value has actually changed setIfChanged(Link.GuiElement, Link.gui_element_prop, val); else Elem = Arg2; % Find the link structure corresponding to Elem ind = arrayfun( @(x)isequal(x.GuiElement, Elem), ... this.Links); Link = this.Links(ind); if length(Link) == 1 updateLinkedElement(this, Link); elseif isempty(Link) warning(['The value of GUI element below cannot ' ... 'be updated as no link for it is found.']); disp(Elem); else warning(['The value of GUI element below cannot ' ... 'be updated, multiple link structures exist.']); disp(Elem); end end end function addToCleanup(this, Obj) this.cleanup_list{end+1} = Obj; end end methods (Access = protected) function kernelDeletedCallback(this, ~, ~) % Switch off the AppBeingDeleted callback in order to prevent % an infinite loop this.Listeners.AppDeleted.Enabled = false; delete(this.App); delete(this); end function f = createPostSetCallback(this, Link) function postSetCallback(~,~) val = Link.getTargetFcn(); if ~isempty(Link.outputProcessingFcn) val = Link.outputProcessingFcn(val); end setIfChanged(Link.GuiElement, Link.gui_element_prop, val); % Optionally execute the update function defined within % the App if ~isempty(this.updateGuiFcn) this.updateGuiFcn(); end end f = @postSetCallback; end % Callback that is assigned to graphics elements as ValueChangedFcn function f = createValueChangedCallback(this, Link) function valueChangedCallback(~, ~) val = Link.GuiElement.Value; if ~isempty(Link.inputProcessingFcn) val = Link.inputProcessingFcn(val); end Link.setTargetFcn(val); if ~isfield(Link, 'Listener') % Update non event based links updateLinkedElements(this); % Optionally execute the update function defined within % the App if ~isempty(this.updateGuiFcn) this.updateGuiFcn(); end end end f = this.createCallbackFcn(@valueChangedCallback); end function f = createGetTargetFcn(~, Obj, prop_name, S) function val = refProp() val = Obj.(prop_name); end function val = subsrefProp(val) val = subsref(Obj.(prop_name), S, val); end if isempty(S) % Faster way to access property f = @refProp; else % More general way to access property f = @subsrefProp; end end function f = createSetTargetFcn(~, Obj, prop_name, S) function assignProp(val) Obj.(prop_name) = val; end function subsasgnProp(val) Obj.(prop_name) = subsasgn(Obj.(prop_name), S, val); end if isempty(S) % Faster way to assign property f = @assignProp; else % More general way to assign property f = @subsasgnProp; end end %% Subroutines of addLink % Parse input and create the base of Link structure function Link = makeLinkBase(~, Elem, prop_ref, varargin) % Parse function inputs p=inputParser(); % GUI control element addRequired(p, 'Elem'); % Target to which the value of GUI element will be linked % relative to the App itself addRequired(p, 'prop_ref', @ischar); % Linked property of the GUI element (can be e.g. 'Color') addParameter(p, 'elem_prop', 'Value', @ischar); % If input_prescaler 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_prescaler', 1, @isnumeric); % Arbitrary processing functions can be specified for input and output. % out_proc_fcn is applied to values before assigning them to gui % elements and in_proc_fcn is applied before assigning % to the linked properties addParameter(p, 'outputProcessingFcn', [], ... @(f)isa(f,'function_handle')); addParameter(p, 'inputProcessingFcn', [], ... @(f)isa(f,'function_handle')); parse(p, Elem, prop_ref, varargin{:}); assert(isempty( ... arrfun(@(x) isequal(p.Results.Elem, x.GuiElement), ... this.Links)), ['Another link for the same GUI element ' ... 'that is attempted to be linked to ' prop_ref ... ' already exists.']) % Create a new link structure Link = struct( ... 'GuiElement', p.Results.Elem, ... 'gui_element_prop', p.Results.elem_prop, ... 'inputProcessingFcn', p.Results.inputProcessingFcn, ... 'outputProcessingFcn', p.Results.outputProcessingFcn, ... 'getTargetFcn', [], ... 'setTargetFcn', [], ... 'Listener', [] ... ); % Lamp indicators is a special case. It is often convenient to % make a lamp indicate on/off state. If a lamp is being linked % to a logical-type variable we therefore assign a dedicated % OutputProcessingFcn that puts logical values in % corresponcence with colors if strcmpi(Elem.Type, 'uilamp') Link.gui_element_prop = 'Color'; % Select between the default on and off colors. Link.outputProcessingFcn = @(x)select(x, ... MyAppColors.lampOn(), MyAppColors.lampOff()); end % Simple scaling is a special case of value processing % functions. if ~ismember('input_prescaler', p.UsingDefaults) if isempty(Link.inputProcessingFcn) && ... isempty(Link.outputProcessingFcn) Link.inputProcessingFcn = ... @(x) (x/p.Result.input_prescaler); Link.outputProcessingFcn = ... @(x) (x*p.Result.input_prescaler); else warning(['input_prescaler is ignored for target ' ... prop_ref 'as inputProcessingFcn or ' ... 'outputProcessingFcn has been already ' ... 'assigned instead.']); end end end function extendMyInstrumentLink(~, Link, Instrument, tag) Cmd = Instrument.CommandList.(tag); % If supplied command does not have read permission, issue a % warning. if isempty(Cmd.readFcn) fprintf('Instrument property ''%s'' is nor readable\n', ... tag); % Try switching the color of the gui element to orange try Link.GuiElement.BackgroundColor = MyAppColors.warning(); catch try Link.GuiElement.FontColor = MyAppColors.warning(); catch end end end % Auto initialize the content of dropdown menues if isequal(Link.GuiElement.Type, 'uidropdown') if all(cellfun(@ischar, Cmd.value_list)) % Capitalized displayed names for beauty Link.GuiElement.Items = cellfun( ... @(x)[upper(x(1)),lower(x(2:end))],... Cmd.value_list, 'UniformOutput', false); else % Items in a dropdown should be strings, so convert if % necessary str_value_list = cell(length(Cmd.value_list), 1); for i=1:length(Cmd.value_list) if ~ischar(Cmd.value_list{i}) str_value_list{i} = num2str(Cmd.value_list{i}); end end Link.GuiElement.Items = str_value_list; end % Assign the list of unprocessed values as ItemsData Link.GuiElement.ItemsData = Cmd.value_list; end end % Decide if getTargetFcn and, correpondingly, ValueChanged % callback needs to be created function bool = checkCreateVcf(~, Elem, elem_prop, Hobj, hobj_prop) if ~strcmp(elem_prop, 'Value') bool = false; return end % Check property attributes Mp = findprop(Hobj, hobj_prop); prop_write_accessible = strcmpi(Mp.SetAccess,'public') && ... (~Mp.Constant) && (~Mp.Abstract); % Check if the GUI element enabled and editable try gui_element_editable = strcmpi(Elem.Enable, 'on'); catch gui_element_editable = true; end % A check for editability is only meaningful for uieditfieds. % Drop-downs also have 'Editable' property, but it corresponds % to the editability of elements and should not have an effect % on assigning callbacks. if (strcmpi(Elem.Type, 'uinumericeditfield') || ... strcmpi(Elem.Type, 'uieditfield')) ... && strcmpi(Elem.Editable, 'off') gui_element_editable = false; end bool = prop_write_accessible && gui_element_editable; % Do not create a new callback if one already exists (typically % it means that a callback was manually defined in AppDesigner) bool = bool && (isprop(Elem, 'ValueChangedFcn') && ... isempty(Elem.ValueChangedFcn)); end % Extract the top-most handle object in the reference, the end % property name and any further subreference function [Hobj, prop, Subs] = parseReference(this, prop_ref) % Make sure the reference starts with a dot and convert to % subreference structure if prop_ref(1)~='.' PropSubs = str2substruct(['.',prop_ref]); else PropSubs = str2substruct(prop_ref); end % Find the handle object to which the end property belongs as % well as the end property name Hobj = this.App; Subs = PropSubs; % Subreference relative to Hobj.(prop) prop = subsref(this.App, PropSubs(1)); for i=1:length(PropSubs)-1 testvar = subsref(this.App, PropSubs(1:end-i)); if isa(testvar, 'handle') Hobj = testvar; Subs = PropSubs(end-i+2:end); prop = subsref(this.App,PropSubs(1:end-i+1)); break end end end end end diff --git a/@MyRsa/MyRsa.m b/@MyRsa/MyRsa.m index f8fab24..7d347a1 100644 --- a/@MyRsa/MyRsa.m +++ b/@MyRsa/MyRsa.m @@ -1,170 +1,178 @@ % Class for controlling Tektronix RSA5103 and RSA5106 spectrum analyzers classdef MyRsa < MyScpiInstrument & MyDataSource & MyCommCont properties (SetAccess = protected, GetAccess = public) acq_trace = [] % Last read trace end methods (Access = public) function this = MyRsa(varargin) P = MyClassParser(this); processInputs(P, this, varargin{:}); this.Trace.unit_x = 'Hz'; this.Trace.unit_y = '$\mathrm{V}^2/\mathrm{Hz}$'; this.Trace.name_y = 'Power'; this.Trace.name_x = 'Frequency'; end end methods (Access = protected) function createCommandList(this) addCommand(this, 'rbw', ':DPX:BAND:RES', ... 'format', '%e', ... 'info', 'Resolution bandwidth (Hz)', ... 'default', 1e3); addCommand(this, 'auto_rbw', ':DPX:BAND:RES:AUTO', ... 'format', '%b', ... 'default', true); addCommand(this, 'span', ':DPX:FREQ:SPAN', ... 'format', '%e', ... 'info', '(Hz)', ... 'default', 1e6); addCommand(this, 'start_freq',':DPX:FREQ:STAR',... 'format', '%e', ... 'info', '(Hz)', ... 'default', 1e6); addCommand(this, 'stop_freq',':DPX:FREQ:STOP',... 'format', '%e', ... 'info', '(Hz)', ... 'default', 2e6); addCommand(this, 'cent_freq',':DPX:FREQ:CENT',... 'format', '%e', ... 'info', '(Hz)', ... 'default', 1.5e6); % Continuous triggering addCommand(this, 'init_cont', ':INIT:CONT', ... 'format', '%b',... 'info', 'Continuous triggering on/off', ... 'default', true); % Number of points in trace addCommand(this, 'point_no', ':DPSA:POIN:COUN', ... 'format', 'P%i', ... 'value_list', {801, 2401, 4001, 10401}, ... 'default', 10401); % Reference level (dB) addCommand(this, 'ref_level',':INPut:RLEVel', ... 'format', '%e',... 'info', '(dB)', ... 'default', 0); % Display scale per division (dBm/div) addCommand(this, 'disp_y_scale', ':DISPlay:DPX:Y:PDIVision',... 'format', '%e', ... 'info', '(dBm/div)', ... 'default', 10); % Display vertical offset (dBm) addCommand(this, 'disp_y_offset', ':DISPLAY:DPX:Y:OFFSET', ... 'format', '%e', ... 'info', '(dBm)', ... 'default', 0); % Parametric commands for i = 1:3 i_str = num2str(i); % Display trace addCommand(this, ['disp_trace',i_str], ... [':TRAC',i_str,':DPX'], ... 'format', '%b', ... 'info', 'on/off', ... 'default', false); % Trace Detection addCommand(this, ['det_trace',i_str],... [':TRAC',i_str,':DPX:DETection'],... 'format', '%s', ... 'value_list', {'AVERage', 'NEGative', 'POSitive'},... 'default', 'average'); % Trace Function addCommand(this, ['func_trace',i_str], ... [':TRAC',i_str,':DPX:FUNCtion'], ... 'format', '%s', ... 'value_list', {'AVERage', 'HOLD', 'NORMal'},... 'default', 'average'); % Number of averages addCommand(this, ['average_no',i_str], ... [':TRAC',i_str,':DPX:AVER:COUN'], ... 'format', '%i', ... 'default', 1); % Count completed averages addCommand(this, ['cnt_trace',i_str], ... [':TRACe',i_str,':DPX:COUNt:ENABle'], ... 'format', '%b', ... 'info', 'Count completed averages', ... 'default', false); end end end methods (Access = public) function readSingle(this, n_trace) fetch_cmd = sprintf('fetch:dpsa:res:trace%i?', n_trace); fwrite(this.Device, fetch_cmd); data = binblockread(this.Device,'float'); readProperty(this, 'start_freq','stop_freq','point_no'); x_vec=linspace(this.start_freq,this.stop_freq,... this.point_no); %Calculates the power spectrum from the data, which is in dBm. %Output is in V^2/Hz power_spectrum = (10.^(data/10))/this.rbw*50*0.001; %Trace object is created containing the data and its units this.Trace.x = x_vec; this.Trace.y = power_spectrum; this.acq_trace = n_trace; %Trigger acquired data event (inherited from MyInstrument) triggerNewData(this); end % Abort data acquisition - function abort(this) + function abortAcq(this) writeCommand(this, ':ABORt'); end % Initiate data acquisition - function init(this) + function initAcq(this) writeCommand(this, ':INIT'); end + % Wait for the current operation to be completed + function val = opc(this) + val = queryCommand(this, '*OPC?'); + if ~isempty(val) + val = val{1}; + end + end + % Extend readHeader function function Hdr = readHeader(this) %Call parent class method and then append parameters Hdr = readHeader@MyScpiInstrument(this); %Hdr should contain single field addParam(Hdr, Hdr.field_names{1}, ... 'acq_trace', this.acq_trace, ... 'comment', 'Last read trace'); end end end diff --git a/Base instrument classes/MyInstrument.m b/Base instrument classes/MyInstrument.m index e99bc79..3855037 100644 --- a/Base instrument classes/MyInstrument.m +++ b/Base instrument classes/MyInstrument.m @@ -1,216 +1,216 @@ % Generic instrument superclass % % Undefined/dummy methods: % queryString(this, cmd) % createCommandList(this) % % These methods are intentionally not introduced as abstract as under % some conditions they are not necessary classdef MyInstrument < dynamicprops properties (Access = public) % Synchronize all properties after setting new value to one auto_sync = true end properties (SetAccess = protected, GetAccess = public) CommandList = struct() % identification string idn_str='' end properties (Dependent = true) command_names end methods (Access = public) function this = MyInstrument(varargin) createCommandList(this); end % Read all parameters of the physical device function read_cns = sync(this) read_ind = structfun(@(x) ~isempty(x.readFcn), ... this.CommandList); read_cns = this.command_names(read_ind); for i=1:length(read_cns) tag = read_cns{i}; read_value = this.CommandList.(tag).readFcn(); % Compare to the previous value and update if different. % Comparison prevents overhead for objects that listen to % the changes of property values. if ~isequal(this.CommandList.(tag).last_value, read_value) % Assign value without writing to the instrument this.CommandList.(tag).Psl.Enabled = false; this.(tag) = read_value; this.CommandList.(tag).Psl.Enabled = true; end end end function addCommand(this, tag, varargin) p=inputParser(); % Name of the command addRequired(p,'tag', @(x)isvarname(x)); % Functions for reading and writing the property value to the % instrument addParameter(p,'readFcn',[], @(x)isa(x, 'function_handle')); addParameter(p,'writeFcn',[], @(x)isa(x, 'function_handle')); % Function applied before writeFcn addParameter(p,'validationFcn',[], ... @(x)isa(x, 'function_handle')); % Function or list of functions executed after updating the % class property value addParameter(p,'postSetFcn',[], @(x)isa(x, 'function_handle')); addParameter(p,'value_list',{}, @iscell); addParameter(p,'default',[]); addParameter(p,'info','', @ischar); parse(p,tag,varargin{:}); assert(~isprop(this, tag), ['Property named ' tag ... ' already exists in the class.']); for fn = fieldnames(p.Results)' this.CommandList.(tag).(fn{1}) = p.Results.(fn{1}); end this.CommandList.(tag).info = ... toSingleLine(this.CommandList.(tag).info); if ~isempty(this.CommandList.(tag).value_list) assert(isempty(this.CommandList.(tag).validationFcn), ... ['validationFcn is already assigned, cannot ' ... 'create a new one based on value_list']); this.CommandList.(tag).validationFcn = ... @(x) any(cellfun(@(y) isequal(y, x),... this.CommandList.(tag).value_list)); end % Create and configure a dynamic property H = addprop(this, tag); this.(tag) = p.Results.default; H.GetAccess = 'public'; H.SetObservable = true; H.SetMethod = createCommandSetFcn(this, tag); if ~isempty(this.CommandList.(tag).writeFcn) H.SetAccess = 'public'; else H.SetAccess = {'MyInstrument'}; end % Listener to PostSet event this.CommandList.(tag).Psl = addlistener(this, tag, ... 'PostSet', @this.commandPostSetCallback); end % Identification - function [str, msg]=idn(this) + function [str, msg] = idn(this) assert(ismethod(this, 'queryString'), ['The instrument ' ... 'class must define queryString method in order to ' ... 'attempt identification.']) try - [str,~,msg]=queryString(this,'*IDN?'); + [str,~,msg] = queryString(this,'*IDN?'); catch ErrorMessage - str=''; - msg=ErrorMessage.message; + str = ''; + msg = ErrorMessage.message; end - this.idn_str=str; + this.idn_str = str; end % Measurement header function Hdr = readHeader(this) sync(this); Hdr = MyMetadata(); % Instrument name is a valid Matalb identifier as ensured by % its set method (see the superclass) addField(Hdr, this.name); % Add identification string as parameter addParam(Hdr, this.name, 'idn', this.idn_str); for i=1:length(this.command_names) cmd = this.command_names{i}; addParam(Hdr, Hdr.field_names{1}, cmd, this.(cmd), ... 'comment', this.CommandList.(cmd).info); end end end methods (Access = protected) % Dummy function that is redefined in subclasses to % incorporate addCommand statements function createCommandList(~) end % Create set methods for dynamic properties function f = createCommandSetFcn(~, tag) function commandSetFcn(this, val) % Validate new value vFcn = this.CommandList.(tag).validationFcn; if ~isempty(vFcn) assert(vFcn(val), ['Value assigned to property ''' ... tag ''' must satisfy ' func2str(vFcn) '.']); end % Store unprocessed value for quick reference in the future % and change tracking this.CommandList.(tag).last_value = val; pFcn = this.CommandList.(tag).postSetFcn; if ~isempty(pFcn) val = pFcn(val); end this.(tag) = val; end f = @commandSetFcn; end % Post set function for dynamic properties - writing the new value % to the instrument and optionally reading it back to confirm the % change function commandPostSetCallback(this, Src, ~) tag = Src.Name; this.CommandList.(tag).writeFcn(this.(tag)); if this.auto_sync sync(this); end end end %% Set and Get methods methods function val = get.command_names(this) val = fieldnames(this.CommandList); end function set.idn_str(this, str) this.idn_str = toSingleLine(str); end end end diff --git a/GUIs/GuiRsa.mlapp b/GUIs/GuiRsa.mlapp index 2c7f88d..72e5f41 100644 Binary files a/GUIs/GuiRsa.mlapp and b/GUIs/GuiRsa.mlapp differ