diff --git a/@MyNewpUsbComm/MyNewpUsbComm.m b/@MyNewpUsbComm/MyNewpUsbComm.m index f5c0b01..7e2ec6b 100644 --- a/@MyNewpUsbComm/MyNewpUsbComm.m +++ b/@MyNewpUsbComm/MyNewpUsbComm.m @@ -1,91 +1,84 @@ classdef MyNewpUsbComm < MySingleton properties (GetAccess = public, SetAccess = private) % Driver in use isbusy = false end properties (Access = public) % An instance of Newport.USBComm.USB class Usb end methods(Access = private) % The constructor of a singleton class should only be invoked from % the instance method. function this = MyNewpUsbComm() disp(['Creating a new instance of ' class(this)]) loadLib(this); end end methods(Access = public) % Load dll function loadLib(this) dll_path = which('UsbDllWrap.dll'); if isempty(dll_path) error(['UsbDllWrap.dll is not found. This library ',... 'is a part of Newport USB driver and needs ',... 'to be present on Matlab path.']) end NetAsm = NET.addAssembly(dll_path); % Create an instance of Newport.USBComm.USB class Type = GetType(NetAsm.AssemblyHandle,'Newport.USBComm.USB'); this.Usb = System.Activator.CreateInstance(Type); end function str = query(this, addr, cmd) - try - waitfor(this, 'isbusy', false); - catch ME - warning(ME.message) - end % Check if the driver is already being used by another process. % A race condition with various strange consequences is % potentially possible if it is. - if this.isbusy - warning('NewportUsbComm is already in use') - end + assert(~this.isbusy, 'Newport Usb Comm is already in use.') this.isbusy = true; % Send query using QueryData buffer. A new buffer needs to be % created every time to ensure the absence of interference % between different queries. QueryData = System.Text.StringBuilder(64); stat = Query(this.Usb, addr, cmd, QueryData); if stat == 0 str = char(ToString(QueryData)); else str = ''; warning('Query to Newport usb driver was unsuccessful.'); end this.isbusy = false; end end methods(Static) % Concrete implementation of the singleton constructor. function this = instance() persistent UniqueInstance if isempty(UniqueInstance) || ~isvalid(UniqueInstance) this = MyNewpUsbComm(); UniqueInstance = this; else this = UniqueInstance; end end end end diff --git a/Logger/@MyLogger/MyLogger.m b/Logger/@MyLogger/MyLogger.m index 29b5e99..5058111 100644 --- a/Logger/@MyLogger/MyLogger.m +++ b/Logger/@MyLogger/MyLogger.m @@ -1,301 +1,301 @@ % Generic logger that executes measFcn according to MeasTimer, stores the % results and optionally continuously saves them. % measFcn should be a function with no arguments. % measFcn need to return a row vector of numbers in order to save the log % in text format or display it. With other kinds of returned values the % log can still be recorded, but not saved or dispalyed. classdef MyLogger < MyGuiCont properties (Access = public, SetObservable = true) % Timer object MeasTimer timer % Function that provides data to be recorded measFcn = @()0 % MyLog object to store the recorded data Record MyLog % Format for displaying readings (column name: value) disp_fmt = '\t%15s:\t%.5g' % Option for daily/weekly creation of a new log file FileCreationInterval duration end properties (SetAccess = protected, GetAccess = public) % If last measurement was succesful % 0-false, 1-true, 2-never measured last_meas_stat = 2 end properties (Access = protected) Metadata = MyMetadata.empty() end events % Event that is triggered each time measFcn is successfully % executed NewMeasurement % Event for transferring data to the collector NewData end methods (Access = public) function this = MyLogger(varargin) P = MyClassParser(this); addParameter(P, 'log_opts', {}, @iscell); addParameter(P, 'enable_gui', false); processInputs(P, this, varargin{:}); this.Record = MyLog(P.Results.log_opts{:}); % Create and confitugure timer this.MeasTimer = timer(); this.MeasTimer.BusyMode = 'drop'; % Fixed spacing mode of operation does not follow the % period very well, but is robust with respect to % function execution delays this.MeasTimer.ExecutionMode = 'fixedSpacing'; this.MeasTimer.TimerFcn = @this.loggerFcn; % Create GUI if necessary this.gui_name = 'GuiLogger'; if P.Results.enable_gui createGui(this); end end function delete(this) % Stop and delete the timer try stop(this.MeasTimer); catch ME warning(['Could not stop measurement timer. Error: ' ... ME.message]); end try delete(this.MeasTimer); catch ME warning(['Could not delete measurement timer. Error: ' ... ME.message]); end end % Redefine start/stop functions for the brevity of use function start(this) if ~isempty(this.FileCreationInterval) && ... isempty(this.Record.FirstSaveTime) % If run in the limited length mode, extend the record % file name createLogFileName(this); end start(this.MeasTimer); end function stop(this) stop(this.MeasTimer); end function bool = isrunning(this) try bool = strcmpi(this.MeasTimer.running, 'on'); catch ME warning(['Cannot check if the measurement timer is on. '... 'Error: ' ME.message]); bool = false; end end % Trigger an event that transfers the data from one log channel % to Daq function triggerNewData(this, varargin) % Since the class does not have Trace property, a Trace must be % supplied explicitly Trace = toTrace(this.Record, varargin{:}); EventData = MyNewDataEvent('Trace',Trace, 'new_header',false); notify(this, 'NewData', EventData); end % Display reading function str = printReading(this, ind) if isempty(this.Record.timestamps) str = ''; return end % Print the last reading if index is not given explicitly if nargin()< 2 ind = length(this.Record.timestamps); end switch ind case 1 prefix = 'First reading '; case length(this.Record.timestamps) prefix = 'Last reading '; otherwise prefix = 'Reading '; end str = [prefix, char(this.Record.timestamps(ind)), newline]; data_row = this.Record.data(ind, :); for i=1:length(data_row) if length(this.Record.data_headers)>=i lbl = this.Record.data_headers{i}; else lbl = sprintf('data%i', i); end str = [str,... sprintf(this.disp_fmt, lbl, data_row(i)), newline]; %#ok end end % Generate a new file name for the measurement record function createLogFileName(this, path, name, ext) [ex_path, ex_name, ex_ext] = fileparts(this.Record.file_name); if ~exist('path', 'var') path = ex_path; end if ~exist('name', 'var') name = ex_name; end if ~exist('ext', 'var') if ~isempty(ex_ext) ext = ex_ext; else ext = this.Record.data_file_ext; end end % Remove the previous time stamp from the file name if exists token = regexp(name, ... '\d\d\d\d-\d\d-\d\d \d\d-\d\d (.*)', 'tokens'); if ~isempty(token) name = token{1}{1}; end % Prepend a new time stamp name = [datestr(datetime('now'),'yyyy-mm-dd HH-MM '), name]; file_name = fullfile(path, [name, ext]); % Ensure that the generated file name is unique file_name = createUniqueFileName(file_name); this.Record.file_name = file_name; end function Mdt = readSettings(this) if isempty(this.Metadata) this.Metadata = MyMetadata('title', class(this)); addParam(this.Metadata, 'meas_period', [], 'comment', ... 'measurement period (s)'); addParam(this.Metadata, 'save_cont', [], 'comment', ... 'If measurements are continuously saved (true/false)'); addParam(this.Metadata, 'file_creation_interval', [], ... 'comment', ['The interval over which new data ' ... 'files are created when saving continuously ' ... '(days:hours:min:sec)']); addParam(this.Metadata, 'log_length_limit', [], ... 'comment', ['The maximum number of points kept ' ... 'in the measurement record']); end % Update parameter values this.Metadata.ParamList.meas_period = this.MeasTimer.Period; this.Metadata.ParamList.save_cont = this.Record.save_cont; this.Metadata.ParamList.file_creation_interval = ... char(this.FileCreationInterval); this.Metadata.ParamList.log_length_limit = ... this.Record.length_lim; Mdt = copy(this.Metadata); end % Configure the logger settings from metadata function writeSettings(this, Mdt) - % Stop the logger if presently running + % Stop the logger if it is presently running stop(this); if isparam(Mdt, 'meas_period') this.MeasTimer.Period = Mdt.ParamList.meas_period; end if isparam(Mdt, 'save_cont') this.Record.save_cont = Mdt.ParamList.save_cont; end if isparam(Mdt, 'file_creation_interval') this.FileCreationInterval = ... duration(Mdt.ParamList.file_creation_interval); end if isparam(Mdt, 'log_length_limit') this.Record.length_lim = Mdt.ParamList.log_length_limit; end end end methods (Access = protected) % Perform measurement and append point to the log function loggerFcn(this, ~, event) Time = datetime(event.Data.time); try meas_result = this.measFcn(); this.last_meas_stat = 1; % last measurement ok catch ME warning(['Logger cannot take measurement at time = ',... datestr(Time) '.\nError: ' ME.message]); this.last_meas_stat = 0; % last measurement not ok return end if this.Record.save_cont && ... ~isempty(this.FileCreationInterval) && ... ~isempty(this.Record.FirstSaveTime) && ... (Time - this.Record.FirstSaveTime) >= ... this.FileCreationInterval % Switch to a new data file createLogFileName(this); end % Append measurement result together with time stamp appendData(this.Record, Time, meas_result); notify(this, 'NewMeasurement'); end end %% Set and get functions methods function set.measFcn(this, val) assert(isa(val, 'function_handle'), ... '''measFcn'' must be a function handle.'); this.measFcn = val; end end end diff --git a/Utility functions/runSession.m b/Utility functions/runSession.m index 27da9dd..0c24aef 100644 --- a/Utility functions/runSession.m +++ b/Utility functions/runSession.m @@ -1,145 +1,154 @@ % Load metadata specified in filename, run all the instruments indicated in % it and configure the settings of those instruments from metadata % parameters function runSession(filename) Mdt = MyMetadata.load(filename); assert(~isempty(Mdt), ['Metadata is not found in the file ''' ... filename '''.']); + disp(['Loading session info from file ' filename '...']) + % SessionInfo contains information about the state of Collector CollMdt = titleref(Mdt, 'SessionInfo'); if length(CollMdt)>1 warning(['Multiple SessionInfo fields are found in the file ' ... 'metadata.']); CollMdt = CollMdt(1); end ProgList = getIcPrograms(); prog_names = {ProgList.name}; if ~isempty(CollMdt) % Get the list of instruments active during the session from the % collector metadata ind = cellfun(@(x)ismember(x, CollMdt.ParamList.instruments), ... prog_names); else % Get the list of instruments as the titles of those metadata % entries that have a corresponding local measurement routine ind = cellfun(@(x)ismember(x, {Mdt.title}), prog_names); end ActiveProgList = ProgList(ind); % Delete all the instruments present in the collector C = MyCollector.instance(); + + disp('Closing the current session...') + flush(C); % Run new instruments and configure their settings for i = 1:length(ActiveProgList) nm = ActiveProgList(i).name; + disp(['Starting ' nm '...']) + % Extract instument options from the collector metadata or assign % default values try collect_header = CollMdt.ParamList.Props.(nm).collect_header; catch collect_header = true; end try has_gui = CollMdt.ParamList.Props.(nm).has_gui; catch has_gui = true; end try gui_position = CollMdt.ParamList.Props.(nm).gui_position; catch gui_position = ''; end % We hedge the operation of running a new instrument so that the % falure of one would not prevent starting the others try if has_gui eval(ActiveProgList(i).run_expr); if ~isempty(gui_position) Instr = getInstrument(C, nm); Fig = findFigure(Instr); original_units = Fig.Units; Fig.Units = 'pixels'; % Set x and y position of GUI figure Fig.Position(1) = gui_position(1); Fig.Position(2) = gui_position(2); % Restore the figure settings Fig.Units = original_units; end else eval(ActiveProgList(i).run_bg_expr); end setInstrumentProp(C, nm, 'collect_header', collect_header); % Configure the settings of instrument object InstrMdt = titleref(Mdt, nm); Instr = getInstrument(C, nm); if ~isempty(InstrMdt) && ismethod(Instr, 'writeSettings') if length(InstrMdt) > 1 warning(['Duplicated entries are found for the ' ... 'instrument with name ''' nm '''.']); InstrMdt = InstrMdt(1); end try writeSettings(Instr, InstrMdt); catch ME warning(['Error while attempting to write serrings '... 'to ''' nm ''': ' ME.message]) end end catch ME warning(['Could not start instrument with name ''' nm ... '''. Error: ' ME.message]) end end % Run apps for i = 1:length(CollMdt.ParamList.apps) try nm = CollMdt.ParamList.apps{i}; % The convention is such that the apps can be instantiated as % classname(), i.e. that their constructor does not have % required input arguments. App = eval(CollMdt.ParamList.AppProps.(nm).class); pos = CollMdt.ParamList.AppProps.(nm).position; if ~isempty(pos) Fig = findFigure(App); original_units = Fig.Units; Fig.Units = 'pixels'; % Set x and y position of figure Fig.Position(1) = pos(1); Fig.Position(2) = pos(2); % Restore the figure settings Fig.Units = original_units; end catch ME warning(['Error while attempting to run an app: ' ME.message]) end end + + disp('Finished loading session.') end