diff --git a/@MyLogger/MyLogger.m b/@MyLogger/MyLogger.m index 413f4aa..09ecbd3 100644 --- a/@MyLogger/MyLogger.m +++ b/@MyLogger/MyLogger.m @@ -1,316 +1,316 @@ % 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 < handle properties (Access = public) % Timer object MeasTimer = timer.empty() % Function that provides data to be recorded measFcn = @()0 % MyLog object to store the recorded data Record = MyLog.empty() % 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.empty() 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); 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; 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 ongoing measurements + % Stop the logger if 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 function set.Record(this, val) assert(isa(val, 'MyLog'), '''Record'' must be a MyLog object') this.Record = val; end function set.MeasTimer(this, val) assert(isa(val, 'timer'), ... '''MeasTimer'' must be a timer object') this.MeasTimer = val; end function set.FileCreationInterval(this, Val) assert(isa(Val, 'duration'), ['''FileCreationInterval'' ' ... 'must be a duration object.']) if ~isempty(Val) Val.Format = 'dd:hh:mm:ss'; end this.FileCreationInterval = Val; end end end diff --git a/Test functions/DataAcquisitionMenu test/testdaqmenu.m b/Test functions/DataAcquisitionMenu test/testdaqmenu.m deleted file mode 100644 index c794d8a..0000000 --- a/Test functions/DataAcquisitionMenu test/testdaqmenu.m +++ /dev/null @@ -1,6 +0,0 @@ - runDPO4034_1; - runRSA5106; - - test_menu=DataAcquisitionMenu('InstrHandles',{GuiScopeDPO4034_1.Instr,GuiRsaRSA5106.Instr}); - - test_daq=MyDaq('daq_menu_handle',test_menu); \ No newline at end of file diff --git a/Test functions/Dummy objects/@MyDummyInstrument/MyDummyInstrument.m b/Test functions/Dummy objects/@MyDummyInstrument/MyDummyInstrument.m new file mode 100644 index 0000000..d131667 --- /dev/null +++ b/Test functions/Dummy objects/@MyDummyInstrument/MyDummyInstrument.m @@ -0,0 +1,57 @@ +% Object for testing data acquisition and header collection functionality + +classdef MyDummyInstrument < MyInstrument & MyDataSource + + properties (Access = public) + point_no = 1000 + end + + methods (Access = public) + function this = MyDummyInstrument() + createCommandList(this); + end + + function readTrace(this) + + % Generate a random trace with the length equal to point_no + this.Trace.x = 1:this.point_no; + this.Trace.y = rand(1, this.point_no); + + triggerNewData(this); + end + + function Lg = createLogger(this, varargin) + function x = getRandomMeasurement() + sync(this); + x = this.cmd3; + end + + Lg = MyLogger(varargin{:}, 'MeasFcn', @getRandomMeasurement); + + Lg.Record.data_headers = {'random 1', 'random 2', ... + 'random 3', 'random 4', 'random 5'}; + end + end + + methods (Access = protected) + function createCommandList(this) + + % cmd1 is read and write accessible, it represents a paramter + % which we can set and which does not change unless we set it + addCommand(this, 'cmd1', ... + 'readFcn', @()this.cmd1, ... + 'writeFcn', @(x)fprintf('cmd 1 write %e\n', x), ... + 'default', rand()); + + addCommand(this, 'cmd2', ... + 'readFcn', @()rand(), ... + 'info', 'read only scalar'); + + % cmd3 is a read only vector + addCommand(this, 'cmd3', ... + 'readFcn', @()rand(1,5), ... + 'info', 'read only vector'); + end + end +end + diff --git a/Test functions/Dummy objects/@MyDummyScpiInstrument/MyDummyScpiInstrument.m b/Test functions/Dummy objects/@MyDummyScpiInstrument/MyDummyScpiInstrument.m new file mode 100644 index 0000000..b4c1b27 --- /dev/null +++ b/Test functions/Dummy objects/@MyDummyScpiInstrument/MyDummyScpiInstrument.m @@ -0,0 +1,74 @@ +% Object for testing data acquisition and header collection functionality + +classdef MyDummyScpiInstrument < MyScpiInstrument & MyDataSource + + properties (Access = public) + point_no = 1000 + end + + methods (Access = public) + function this = MyDummyScpiInstrument() + createCommandList(this); + end + + function readTrace(this) + + % Generate a random trace with the length equal to point_no + this.Trace.x = 1:this.point_no; + this.Trace.y = rand(1, this.point_no); + + triggerNewData(this); + end + + % Replacement method that emulates communication with physical + % device + function resp_str = queryString(this, query_str) + query_cmds = strsplit(query_str, ';'); + + resp_str = ''; + for i = 1:length(query_cmds) + cmd = query_cmds{i}; + if ~isempty(cmd) + switch cmd + case 'COMMAND1' + tmp_resp = sprintf(this.CommandList.cmd1.format, ... + this.cmd1); + case 'COMMAND2' + tmp_resp = sprintf(this.CommandList.cmd2.format, ... + this.cmd2); + case 'COMMAND3' + tmp_resp = sprintf(this.CommandList.cmd3.format, ... + this.cmd3); + otherwise + tmp_resp = ''; + end + + resp_str = [resp_str ';' tmp_resp]; %#ok + end + end + end + + % writeString does nothing + function writeString(this, str) %#ok + end + end + + methods (Access = protected) + function createCommandList(this) + + addCommand(this, 'cmd1', 'COMMAND1', ... + 'format', '%e', ... + 'info', 'regular read/write numeric command'); + + addCommand(this, 'cmd2', 'COMMAND2', ... + 'format', '%s', ... + 'info', 'regular read/write string command', ... + 'default', 'val'); + + addCommand(this, 'cmd3', 'COMMAND3', ... + 'format', '%i,%i,%i', ... + 'info', 'read/write vector'); + end + end +end +