diff --git a/@MyCollector/MyCollector.m b/@MyCollector/MyCollector.m index fa21ecc..59e60b8 100644 --- a/@MyCollector/MyCollector.m +++ b/@MyCollector/MyCollector.m @@ -1,158 +1,150 @@ classdef MyCollector < handle & matlab.mixin.Copyable properties (Access=public, SetObservable=true) InstrList % Structure accomodating instruments InstrProps % Properties of instruments MeasHeaders collect_flag end properties (Access=private) Listeners end properties (Dependent=true) running_instruments end events NewDataWithHeaders end methods (Access=public) function this=MyCollector(varargin) p=inputParser; addParameter(p,'InstrHandles',{}); parse(p,varargin{:}); this.collect_flag=true; if ~isempty(p.Results.InstrHandles) cellfun(@(x) addInstrument(this,x),p.Results.InstrHandles); end this.MeasHeaders=MyMetadata(); this.InstrList=struct(); this.InstrProps=struct(); this.Listeners=struct(); end function delete(this) cellfun(@(x) deleteListeners(this,x), this.running_instruments); end function addInstrument(this,instr_handle,varargin) p=inputParser; addParameter(p,'name','UnknownDevice',@ischar) parse(p,varargin{:}); %Find a name for the instrument if ~ismember('name',p.UsingDefaults) name=p.Results.name; elseif isprop(instr_handle,'name') && ~isempty(instr_handle.name) name=genvarname(instr_handle.name, this.running_instruments); else name=genvarname(p.Results.name, this.running_instruments); end %We add only classes that have readHeaders functionality if contains('readHeader',methods(instr_handle)) %Defaults to read header this.InstrProps.(name).header_flag=true; this.InstrList.(name)=instr_handle; else error(['%s does not have a readHeader function,',... ' cannot be added to Collector'],name) end %If the added instrument has a newdata event, we add a listener for it. if contains('NewData',events(this.InstrList.(name))) this.Listeners.(name).NewData=... addlistener(this.InstrList.(name),'NewData',... @(~,InstrEventData) acquireData(this, InstrEventData)); end %Cleans up if the instrument is closed this.Listeners.(name).Deletion=... addlistener(this.InstrList.(name),'ObjectBeingDestroyed',... @(~,~) deleteInstrument(this,name)); end function acquireData(this,InstrEventData) src=InstrEventData.Source; %Collect the headers if the flag is on if this.collect_flag this.MeasHeaders=MyMetadata(); addField(this.MeasHeaders,'AcquiringInstrument') if isprop(src,'name') name=src.name; else name='Not Accessible'; end addParam(this.MeasHeaders,'AcquiringInstrument',... 'Name',name); acquireHeaders(this); %We copy the MeasHeaders to the trace. 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 TmpMetadata=readHeader(this.InstrList.(name)); addMetadata(this.MeasHeaders, TmpMetadata); end end end function clearHeaders(this) this.MeasHeaders=MyMetadata(); end - function Trace=getTrace(this,name) - assert(isopen(this,name),'%s is not an open instrument'); - assert(isprop(this.InstrList.(name),'Trace'),... - 'Cannot get trace, %s does not have a Trace property',... - name); - Trace=this.InstrList.(name).Trace; - end - function bool=isrunning(this,name) assert(~isempty(name),'Instrument name must be specified') assert(ischar(name),... 'Instrument name must be a character, not %s',... class(name)); bool=ismember(this.running_instruments,name); end end methods (Access=private) function triggerNewDataWithHeaders(this,InstrEventData) % in EventData pass information about the instrument EventData = MyNewDataEvent(); EventData.InstrEventData = InstrEventData; notify(this,'NewDataWithHeaders',EventData); end %deleteListeners is in a separate file deleteListeners(this, obj_name); function deleteInstrument(this,name) %We remove the instrument this.InstrList=rmfield(this.InstrList,name); this.InstrProps=rmfield(this.InstrProps,name); deleteListeners(this,name); end end methods function running_instruments=get.running_instruments(this) running_instruments=fieldnames(this.InstrList); end end end diff --git a/@MyInstrument/MyInstrument.m b/@MyInstrument/MyInstrument.m index 771c67e..5b3f959 100644 --- a/@MyInstrument/MyInstrument.m +++ b/@MyInstrument/MyInstrument.m @@ -1,237 +1,243 @@ classdef MyInstrument < dynamicprops & MyInputHandler properties (Access=public) name=''; interface=''; address=''; Device %Device communication object Trace %Trace object for storing data end properties (GetAccess=public, SetAccess=protected) idn_str=''; % identification string end properties (Constant=true) % Default parameters for device connection DEFAULT_INP_BUFF_SIZE = 1e7; % buffer size bytes DEFAULT_OUT_BUFF_SIZE = 1e7; % buffer size bytes DEFAULT_TIMEOUT = 10; % Timeout in s end events - NewData; + NewData + NewParameter 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); 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{:}); % Create an empty trace this.Trace=MyTrace(); % Create dummy device object that supports properties this.Device=struct(); this.Device.Status='not connected'; % Interface and address can correspond to an entry in the list % of local instruments. Read this entry in such case. if strcmpi(interface, '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 if ~isfield(InstrumentList.(instr_name), 'interface') error(['InstrumentList entry ', instr_name,... ' has no ''interface'' field']); else this.interface = InstrumentList.(instr_name).interface; end if ~isfield(InstrumentList.(instr_name), 'address') error(['InstrumentList entry ', instr_name,... ' has no ''address'' field']); else this.address = InstrumentList.(instr_name).address; end % Assign name automatically, but not overwrite if % already specified if isempty(this.name) this.name = instr_name; end end % Connecting device creates a communication object, % but does not attempt communication connectDevice(this); 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 %Triggers event for acquired data function triggerNewData(this) notify(this,'NewData') end + %Triggers event for property read from device + function triggerNewParameter(this) + notify(this,'NewParameter') + end + % Read all the relevant instrument properties and return as a % MyMetadata object. % Dummy method that needs to be re-defined by a parent class function Hdr=readHeader(this) Hdr=MyMetadata(); % Generate valid field name from instrument name if present and % class name otherwise if ~isempty(this.name) field_name=genvarname(this.name); else field_name=class(this); end addField(Hdr, field_name); % Add identification string as parameter addParam(Hdr, field_name, 'idn', this.idn_str); end %% Connect, open, configure, identificate and close the device % Connects to the device, explicit indication of interface and % address is for ability to handle instr_list as interface function connectDevice(this) int_list={'constructor','visa','tcpip','serial'}; if ~ismember(lower(this.interface), int_list) warning(['Device is not connected, unknown interface ',... this.interface,'. Valid interfaces are ',... '''constructor'', ''visa'', ''tcpip'' and ''serial''']) return end try switch lower(this.interface) % Use 'constructor' interface to connect device with % more that one parameter, specifying its address case 'constructor' % in this case the 'address' is a command % (ObjectConstructorName), e.g. as returned by the % instrhwinfo, that creates communication object % when executed this.Device=eval(this.address); case 'visa' % visa brand is 'ni' by default this.Device=visa('ni', this.address); case 'tcpip' % Works only with default socket. Use 'constructor' % if socket or other options need to be specified this.Device=tcpip(this.address); case 'serial' this.Device=serial(this.address); otherwise error('Unknown interface'); end configureDeviceDefault(this); catch warning(['Device is not connected, ',... 'error while creating communication object.']); end end % Opens the device if it is not open. Does not throw error if % device is already open for communication with another object, but % tries to close existing connections instead. 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 %% Identification % Attempt communication and identification of the device function [str, msg]=idn(this) was_open=isopen(this); try openDevice(this); [str,~,msg]=query(this.Device,'*IDN?'); catch ErrorMessage str=''; msg=ErrorMessage.message; end this.idn_str=str; % Leave device in the state it was in the beginning if ~was_open try closeDevice(this); catch end end end end end \ No newline at end of file diff --git a/GUIs/GuiScope.mlapp b/GUIs/GuiScope.mlapp index 1dd40b7..447ad2e 100644 Binary files a/GUIs/GuiScope.mlapp and b/GUIs/GuiScope.mlapp differ diff --git a/Utility functions/App utilities/deleteInstrGui.m b/Utility functions/App utilities/deleteInstrGui.m index 5262a46..bbb7f8b 100644 --- a/Utility functions/App utilities/deleteInstrGui.m +++ b/Utility functions/App utilities/deleteInstrGui.m @@ -1,14 +1,22 @@ % Delete Instrument object, clearing the global variable corresponding to % gui name and then delete gui itself -function deleteInstrGui(app) +function deleteInstrGui(app) + %Deletes the listeners + try + lnames=fieldnames(this.Listeners); + for i=1:length(lnames) + delete(this.Listeners.(lnames{i})); + end + catch + end try delete(app.Instr); catch end try evalin('base', sprintf('clear(''%s'')', app.name)); catch end delete(app) end diff --git a/Utility functions/App utilities/initInstrGui.m b/Utility functions/App utilities/initInstrGui.m new file mode 100644 index 0000000..5038d84 --- /dev/null +++ b/Utility functions/App utilities/initInstrGui.m @@ -0,0 +1,18 @@ +% +function initInstrGui(app,Instrument) + app.Instr=Instrument; + % Send identification request to instrument + idn(app.Instr); + + app.Listeners.NewParameter=... + addlistener(app.Instr,'NewParameter',@(~,~)updateGui(app)); + + if ismethod(app,'updatePlot') + app.Listeners.NewData=... + addlistener(app.Instr,'NewData',@(~,~)updatePlot(app)); + end + + createControlLinks(app); + readPropertyHedged(app.Instr,'all'); +end +