diff --git a/@MyCollector/MyCollector.m b/@MyCollector/MyCollector.m index d6d2dc9..a5c4a43 100644 --- a/@MyCollector/MyCollector.m +++ b/@MyCollector/MyCollector.m @@ -1,192 +1,212 @@ classdef MyCollector < MySingleton & matlab.mixin.Copyable properties (Access = public, SetObservable = true) InstrList = struct() % Structure accomodating instruments InstrProps = struct() % Properties of instruments MeasHeaders collect_flag = true end properties (Access = private) Listeners = struct() end properties (Dependent = true) running_instruments end events NewDataWithHeaders end methods (Access = private) % Constructor of a singleton class must be private function this = MyCollector(varargin) p = inputParser; addParameter(p,'InstrHandles',{}); parse(p,varargin{:}); if ~isempty(p.Results.InstrHandles) cellfun(@(x) addInstrument(this,x),p.Results.InstrHandles); end this.MeasHeaders = MyMetadata(); end end methods (Access=public) function delete(this) cellfun(@(x) deleteListeners(this,x), this.running_instruments); end function addInstrument(this, name, Instrument) assert(isvarname(name), ['Instrument name must be a valid ' ... 'MATLAB variable name.']) assert(~ismember(name, this.running_instruments), ... ['Instrument ' name ' is already present in the ' ... 'collector. Delete the existing instrument before ' ... 'adding a new one with the same name.']) if ismethod(Instrument, 'readSettings') %Defaults to read header this.InstrProps.(name).header_flag = true; else % If the class does not have a header generation function, % it can still be added to the collector and transfer data % to Daq this.InstrProps.(name).header_flag = false; warning(['%s does not have a readSettings function, ',... 'measurement headers will not be collected from ',... 'this instrument.'],name) end this.InstrList.(name) = instr_handle; % If the added instrument has a newdata event, we add a % listener for it. if ismember('NewData', events(this.InstrList.(name))) this.Listeners.(name).NewData=... addlistener(this.InstrList.(name),'NewData',... @(~, EventData) acquireData(this, name, EventData)); end %Cleans up if the instrument is closed this.Listeners.(name).Deletion=... addlistener(this.InstrList.(name),'ObjectBeingDestroyed',... @(~,~) deleteInstrument(this,name)); end + % Store instrument GUI + function addInstrumentGui(this, instr_name, Gui) + assert(ismember(instr_name, this.running_instruments), ... + 'Name must correspond to one of the running instruments.') + this.InstrProps.(name).Gui = Gui; + end + + % Store instrument GUI + function Gui = getInstrumentGui(this, instr_name) + assert(ismember(instr_name, this.running_instruments), ... + 'Name must correspond to one of the running instruments.') + + if isfield(this.InstrProps.(name), 'Gui') && ... + isvalid(this.InstrProps.(name).Gui) + Gui = this.InstrProps.(name).Gui; + else + Gui = []; + end + end + function acquireData(this, name, InstrEventData) src = InstrEventData.Source; % Check that event data object is MyNewDataEvent, % and fix otherwise if ~isa(InstrEventData,'MyNewDataEvent') InstrEventData = MyNewDataEvent(); InstrEventData.new_header = true; InstrEventData.Trace = copy(src.Trace); end % Add instrument name InstrEventData.src_name = name; % Collect the headers if the flag is on and if the triggering % instrument does not request suppression of header collection if this.collect_flag && InstrEventData.new_header this.MeasHeaders=MyMetadata(); %Add field indicating the time when the trace was acquired addTimeField(this.MeasHeaders, 'AcquisitionTime') addField(this.MeasHeaders,'AcquiringInstrument') %src_name is a valid matlab variable name as ensured by %its set method addParam(this.MeasHeaders,'AcquiringInstrument',... 'Name', InstrEventData.src_name); acquireHeaders(this); %We copy the MeasHeaders to both copies of the trace - the %one that is with the source and the one that is forwarded %to Daq. InstrEventData.Trace.MeasHeaders=copy(this.MeasHeaders); src.Trace.MeasHeaders=copy(this.MeasHeaders); end triggerNewDataWithHeaders(this,InstrEventData); end %Collects headers for open instruments with the header flag on function acquireHeaders(this) for i=1:length(this.running_instruments) name=this.running_instruments{i}; if this.InstrProps.(name).header_flag try TmpMetadata=readSettings(this.InstrList.(name)); addMetadata(this.MeasHeaders, TmpMetadata); catch warning(['Error while reading metadata from %s. '... 'Measurement header collection is switched '... 'off for this instrument.'],name) this.InstrProps.(name).header_flag=false; end end end end function clearHeaders(this) this.MeasHeaders=MyMetadata(); end function bool=isrunning(this,name) assert(~isempty(name),'Instrument name must be specified') assert(ischar(name)&&isvector(name),... 'Instrument name must be a character vector, not %s',... class(name)); bool=ismember(name,this.running_instruments); end function deleteInstrument(this,name) if isrunning(this,name) %We remove the instrument this.InstrList=rmfield(this.InstrList,name); this.InstrProps=rmfield(this.InstrProps,name); deleteListeners(this,name); end end end methods(Static) % Concrete implementation of the singletone constructor. function this = instance() persistent UniqueInstance if isempty(UniqueInstance)||(~isvalid(UniqueInstance)) disp('Creating new instance of MyCollector') this = MyCollector(); UniqueInstance = this; else this = UniqueInstance; end end end methods (Access=private) function triggerNewDataWithHeaders(this,InstrEventData) notify(this,'NewDataWithHeaders',InstrEventData); end %deleteListeners is in a separate file deleteListeners(this, obj_name); end methods function running_instruments = get.running_instruments(this) running_instruments = fieldnames(this.InstrList); end end end diff --git a/Utility functions/runInstrument.m b/Utility functions/runInstrument.m index 5cb5c14..55c26e0 100644 --- a/Utility functions/runInstrument.m +++ b/Utility functions/runInstrument.m @@ -1,93 +1,98 @@ % Create instrument instance and add it to the collector function Instr = runInstrument(name, instr_class, varargin) % Get the unique instance of Collector Collector = MyCollector.instance(); % Check if the instrument is already running if ismember(name, Collector.running_instruments) % If instrument is already present in the Collector, do not create % a new object, but try taking the existing one. disp([name, ' is already running. Assigning the existing ', ... 'object instead of running a new one.']); try Instr = Collector.InstrList.(name); catch error('Could not assign instrument %s from Collector',name); end return end % Create a new instrument object if nargin()==1 % Load instr_class, interface, address and other startup arguments % from InstrumentList InstrumentList = getLocalSettings('InstrumentList'); assert(isfield(InstrumentList, name), [name ' must be a field ' ... 'of InstrumentList.']) assert(isfield(InstrumentList.(name), 'control_class'), ... ['InstrumentList entry ' name ... ' has no ''control_class'' field.']) instr_class = InstrumentList.(name).control_class; instr_args = {}; % instrument startup arguments if isfield(InstrumentList.(name), 'interface') instr_args = [instr_args, {'interface', ... InstrumentList.(name).interface}]; end if isfield(InstrumentList.(name), 'address') instr_args = [instr_args, {'address', ... InstrumentList.(name).address}]; end % Make a list of optional name-value pairs. Put the options on the % left-hand side of the list so that they could not overshadow % 'interface' and 'address' if isfield(InstrumentList.(name), 'StartupOpts') Opts = InstrumentList.(name).StartupOpts; instr_args = [struct2namevalue(Opts), instr_args]; end else % Case when all the arguments are supplied explicitly instr_args = varargin; end % Create an instrument instance and store it in Collector Instr = feval(instr_class, instr_args{:}); addInstrument(Collector, name, Instr); + % Assign instrument handles to a variable in global workspace + if ~isValidBaseVar(name) + assignin('base', name, Instr); + end + try % Open communication. Typically instrument commands will re-open % the communication object if it is closed, but keepeing it open % speeds communication up. if ismethod(Instr, 'openComm') openComm(Instr); end % Send identification request to the instrument if ismethod(Instr, 'sync') sync(Instr); end % Send identification request to the instrument if ismethod(Instr, 'idn') idn(Instr); end catch ME warning(['Could not start communication with ' name ... '. Error: ' ME.message]); end end diff --git a/Utility functions/runInstrumentWithGui.m b/Utility functions/runInstrumentWithGui.m index 199fd3c..861e3c6 100644 --- a/Utility functions/runInstrumentWithGui.m +++ b/Utility functions/runInstrumentWithGui.m @@ -1,58 +1,54 @@ % Create instrument instance with gui and add it to the collector -function [Instr, GuiInstr] = runInstrumentWithGui(name, instr_class, interface, address, gui) - % Run instrument +function [Instr, GuiInstr] = runInstrumentWithGui(name, instr_class, gui, varargin) + + % Get the unique instance of Collector + Collector = MyCollector.instance(); + + % Run instrument first if nargin==1 + % load parameters from InstrumentList InstrumentList = getLocalSettings('InstrumentList'); - if ~isfield(InstrumentList, name) - error('%s is not a field of InstrumentList',... - name); - end - if ~isfield(InstrumentList.(name), 'gui') - error(['InstrumentList entry ', name,... - ' has no ''gui'' field']); - else - gui = InstrumentList.(name).gui; - end + assert(isfield(InstrumentList, name), [name ' must be a field ' ... + 'of InstrumentList.']) + + assert(isfield(InstrumentList.(name), 'gui'), ... + ['InstrumentList entry ' name ' has no ''gui'' field.']) + + gui = InstrumentList.(name).gui; Instr = runInstrument(name); - elseif nargin==5 - % Case when all the arguments are supplied explicitly - Instr = runInstrument(name, instr_class, interface, address); else - error(['Wrong number of input arguments. ',... - 'Function can be called as f(name) or ',... - 'f(name, instr_class, interface, address, gui)']) + + % All the arguments are supplied explicitly + Instr = runInstrument(name, instr_class, varargin{:}); end - % Run gui and assign handles to variable in global workspace - gui_name = ['Gui',name]; - if ~isValidBaseVar(gui_name) - % If gui is not present in the base workspace, create it - GuiInstr = feval(gui, Instr); - if isprop(GuiInstr,'name') - GuiInstr.name = gui_name; - end - % Store gui handle in a global variable - assignin('base', GuiInstr.name, GuiInstr); - % Display instrument's name if given - fig_handle=findfigure(GuiInstr); - if ~isempty(fig_handle) - fig_handle.Name=char(name); + % Check if the instrument already has GUI + Gui = getInstrumentGui(this, name); + if isempty(Gui) + + % Run a new GUI and store it in the collector + Gui = feval(gui, Instr); + addInstrumentGui(this, name, Gui); + + % Display the instrument's name + Fig = findfigure(Gui); + if ~isempty(Fig) + Fig.Name = char(name); else warning('No UIFigure found to assign the name') end else - % Otherwise return gui from base workspace - GuiInstr = evalin('base',['Gui',name]); + + % Bring the window of existing GUI to the front try - % bring app figure on top of other windows - Fig = findfigure(GuiInstr); + Fig = findfigure(Gui); Fig.Visible = 'off'; Fig.Visible = 'on'; catch end end end