Code covered by the BSD License  

Highlights from
CO2gui - lab control and automation

CO2gui - lab control and automation

by

 

06 Jan 2010 (Updated )

Software used for controlling and data logging lab equipment.

tharbprobjcomm(type, serialObject, command)
function response = tharbprobjcomm(type, serialObject, command)
% THARBPROBJCOMM Reads and writes data from Thar BPRs
% response = tharbprobjcomm(type, serialObject, command) reads from or
% writes information to Thar BPRs connected via RS232 serial ports. type is
% 'read', 'readnoflush, 'write' or 'writesave'.  'Read' fetches the newest
% data, 'readnoflush' reads the most recent data to go into the input
% buffer, 'write' sends commmands, and 'writesave' leaves a long pause on
% the end - this is to be used when saving data to the BPR as there is a
% long pause before you can control it again.  serialObject is the valid
% serial object and command the command to be sent (string). If reading a
% value, function responds with the requested value.  The command can be
% omitted ignored if values are being read, as this is ignored.  For
% sending multiple keystrokes (commands), then send them in one line as a
% string, so that there is the proper separation between each keystroke. If
% there is only one character as the command, the read commands can be
% performed immediately afterwards much more quickly.

% Normal usage:

% e.g. 1 response = tharbprobjcomm('read', serialObject) - returns the
% current contents of the screen as a 4x1 cell array.  Empty lines are
% returned as [].

% e.g. 2 response = tharbprobjcomm('readnoflush', serialObject) - returns
% the most recent data to fill the input buffer - use this for when in a
% sub-menu where the screen is not regularly updated

% e.g. 3 tharbprobjcomm('write', serialObject, 19) - equivalent to pressing
% the "F3" key on the hand-held controller.

% e.g. 4 tharbprobjcomm('writesave', serialObject, [20, uint8('A100'), 13,
% 13, uint8('Y')]) - presses F4 to enter the profile setup menu, chooses
% profile A, types in 100 (bar) as the new set pressure, presses enter to
% confirm that, presses enter again to skip past the alarm pressure, then
% presses 'Y' to save the settings - the BPR will not respond for a while
% (currently set to 5.5 s).

% Range:

% type = 'read' or 'write'

% serialObject = valid serial object

% command = string or numeric vector


% checks the number of input arguments
error(nargchk(2, 3, nargin))

% error handling
if ~isvector(type) || ~any(strcmp(type, {'read', 'readnoflush', 'readfast', 'write', 'writesave'}))
    % only allowed these types of commands
    error('type must be "read", "readnoflush", "readfast", "write", or "writesave".')
    
elseif ~isserial(serialObject) || (~isfield(serialObject.UserData, 'realTermHandle') && ~isrunning(serialObject))
    % the serial object must be valid and open to send or receive data
    % (unless there is a realterm handle available)
    error('serialObject must be a valid open serial object')
    
elseif ~isserial(serialObject) || (isfield(serialObject.UserData, 'realTermHandle') && ~isrunning(serialObject.UserData.realTermHandle))
    % if the handle is there, the object must be running (PortOpen is 1)
    error('If the Realterm handle is present, the object must be connected via Realterm.')
    
elseif isfield(serialObject.UserData, 'realTermHandle') && (~isfield(serialObject.UserData, 'captureFileID') || ~isfid(serialObject.UserData.captureFileID))
    % if using realterm, the capture file ID must be there
    error('If using Realterm, the capture file handle must be in the serial object''s UserData.')
    
elseif strcmp(type, 'write') && nargin < 3
    % obviously if you specify write, you need to supply a command
    error('A command must be specified to write to the device.')
    
elseif nargin >= 3 && ((~ischar(command) && ~isnumeric(command)) || any(command < 0) || any(command > 127) || (isnumeric(command) && ((~isscalar(command) && ~isvector(command)) || any(~isreal(command)) || any(isnan(command)))))
    % limits the characters that can be used in the command
    error('command must be a string (ASCII 0-127) or a numeric vector with values between 0 and 127.')
end

% if the real term handle is there, use a different set of protocols
if isfield(serialObject.UserData, 'realTermHandle')
    % checks that its not ready to be reconnected (current threshold is 1.5
    % days, although it seems to be stable up to 2.5-3 days in practice) -
    % needs some error handling incorporating? - now changed to 0.75 day
    % because of some spurious behaviour spotted
    
    % DEBUG
    %now - serialObject.UserData.createdTime
    
    if isfield(serialObject.UserData, 'createdTime') && now - serialObject.UserData.createdTime > 0.75
        % stop data capture (infernal thing doesn't work well enough with
        % structures
        %serialObject.UserData.realTermHandle.StopCature
        invoke(serialObject.UserData.realTermHandle, 'StopCapture')        
                        
        % disconnect - currently unused as it doesn't seem to be necessary
        % - if we need to go a step further and restart the realterm
        % process, then we are better off using the realtermfclose and
        % realtermfclose commands instead
        %serialObject.UserData.realTermHandle.PortOpen = 0;
        
        % closes the capture file
        fclose(serialObject.UserData.captureFileID);
        
        % deletes the capture file
        delete(serialObject.UserData.realTermHandle.CaptureFile)
        
        % reconnects
        %serialObject.UserData.realTermHandle.PortOpen = 1;
                
        % restarts capture (same as above)
        %serialObject.UserData.realTermHandle.StartCapture
        invoke(serialObject.UserData.realTermHandle, 'StartCapture')
        
        % opens the file in matlab and saves it back to the user data
        serialObject.UserData.captureFileID = fopen(serialObject.UserData.realTermHandle.CaptureFile);
        
        % resets the timer
        serialObject.UserData.createdTime = now;
    end        
    
    % if writing, there is nothing to read
    if any(strfind(type, 'write'))
        % if the command was numeric, convert it into character form
        if isnumeric(command)
            % change it
            command = char(command);
        end

        % loops round to send commands one at a time
        for m = 1:length(command)
            % sends command to BPR
            try
                % sends command
                realtermfwrite(serialObject.UserData.realTermHandle, command(m))

            catch
                % errors
                error('Command not sent.')
            end

            % each command needs a pause for separation (annoyingly)
            %pause(0.7)
            % DEBUG temporarily increased for supposed improved stability
            pause(1)
        end

        % if the 'writesave' form has been used, need to leave a long pause
        % before the BPR starts working again (this is after saving
        % information, e.g. set pressures or PID constants)
        if strcmp(type, 'writesave')
            % long pause (tested)
            pause(7)
            
        else
            % add an extra short pause at the end
            %pause(0.8)
            
            % DEBUG - for the same reasons as above
            pause(1.5)
        end

    elseif any(strfind(type, 'read'))
        % if it was a no need for a flush, leave a pause
        if strcmp(type, 'readnoflush')
            % this is designed to be used just after a keypress is done
            % (returns '[1s' if successful)) - if in a submenu, no update
            % values are read out

            % leaves a short pause to allow the buffer to fill with all of the
            % information - otherwise information could be missed (this has not
            % been tested - it may need to be extended, or may possibly even by
            % shortened)

            % leave a pause
            pause(0.6)
            
        elseif strcmp(type, 'read')
            % flushes the buffer DEBUG
            dummy = serialflush(serialObject);
            %disp(char(dummy'))
        end

        % if theres not much data there...
        if any(strcmp(type, {'read', 'readfast'}))
            % defines the minimum number of characters needed for a successful and
            % reliable read
            minChars = 200;
        
            % tries to get some more to fill up to the minimum (times out
            % after 1 second)
            data = realtermfread(   serialObject.UserData.realTermHandle,...
                                    serialObject.UserData.captureFileID,...
                                    1,...
                                    minChars,...
                                    '*char')';
                                    
            % DEBUG
            %disp(data)
            
            % if everything wasn't read out
            if numel(data) < minChars
                % errors
                error('Read unsuccessful - timed out.')
            end
            
        elseif strcmp(type, 'readnoflush')
            % read it all out
            data = fread(serialObject.UserData.captureFileID, '*char')';
        end
        
        % if its empty
        if isempty(data)
            % error
            error('No available data - probably in a sub-menu - try pressing enter a few times and then N to get out of the menu')
        end

        % only uses the last 150 characters - reduces the burden on
        % textscan for faster operation - the screen is 4 lines x 20
        % characters, each with a 4 character line header and a single
        % (ASCII 27) terminator (more like a start of line really) - the
        % extra is for a buffer since sometimes keypresses are responded to
        %data = data(end - min([numel(data) - 1, minChars - 1]):end);

        % textscans the data
        data = textscan(data, '%s', 'delimiter', serialObject.Terminator);
        
        % errors if data is empty
        if isempty(data)
            % errors
            error('No terminators found in data.')
        end
        
        % remove the first line (theyre probably not whole
        % lines)
        data = data{1}(2:end);
        
        % slightly different behaviour depending on the read type
        if any(strcmp(type, {'read', 'readfast'}))
            % also remove the last line (also probably a partial line)
            data(end) = [];

            % defines the line to remove
            m = 4;
            parseData = false;

            % while the data is empty
            while all(~parseData) && m > 0
                % find all incidences of the mth line
                parseData = ~cellfun('isempty', strfind(data, sprintf('[%d;1f', m)));

                % decrease m
                m = m - 1;
            end

            % save the parsed data - i.e. the last incidence of each line
            data = data(1:find(parseData, 1, 'last'));
        end

        % pre-allocates the answer
        response = cell(4, 1);

        % the encoding is the ANSI escape code sequence

        % reorders lines into order
        for m = 1:4
            % finds the lines which are '[m;1' - this returns a cell array with
            % a 1 for the correct line, and [] for the wrong line - so can't
            % use simple logical indexing
            index = find(~cellfun('isempty', strfind(data, sprintf('[%d;1f', m))), 1, 'last');

            % assigns the line if it wasn't empty
            if ~isempty(index)
                % fills the box
                response{m} = data{index};
            end
        end

        % strips out the first 5 characters of each line (the '[x;1f' part)
        for n = 1:numel(response)
            % if the line is not empty...
            if ~isempty(response{n})
                % changes the line
                response{n} = response{n}(6:end);
            end
        end
        
        % DEBUG
        %for o = 1:numel(response)
            % displays a line
            %disp(response{o})
        %end
    end
    
else
    % standard MATLAB serial protocols - if writing, there is nothing to read (sort of)
    if any(strfind(type, 'write'))
        % loops round to send commands one at a time
        for m = 1:length(command)
            % sends command (note the non-use of 'async' as it takes its
            % sweet time to send it and does not respond properly
            % otherwise)
            fwrite(serialObject, command(m))

            % if there is some data remaining...
            if serialObject.BytesAvailable
                % currently justs read out everything that is there but ignores it
                fread(serialObject);
            end

            % each command needs a pause for separation (annoyingly)
            pause(0.6)
        end

        % if the 'writesave' form has been used, need to leave a long pause
        % before the BPR starts working again (this is after saving
        % information, e.g. set pressures or PID constants)
        if strcmp(type, 'writesave')
            % long pause (tested)
            pause(7)
        end

    elseif any(strfind(type, 'read'))
        % slightly different read behaviour depending on whether the menu is
        % being continually updated or not
        if any(strcmp(type, {'read', 'readfast'}))
            % always flushes the input buffer before sending the command to avoid
            % any weirdness if any of the previous commands timed out or failed

            % if there is some data remaining...
            if serialObject.BytesAvailable
                % currently justs read out everything that is there but ignores it
                fread(serialObject);
            end

            % reads out the first line then discards it as the the first line is
            % often half of the previous line
            discard = fscanf(serialObject);

            % if the last scan is empty, we are in a submenu where the screen
            % is not repeatedly refreshed
            if isempty(discard)
                % error
                error('No available data - probably in a sub-menu - try pressing enter a few times and then N to get out of the menu')
            end

            % pre-allocates
            data = cell(4, 1);

            % reads 5 lines out (if a key has just been pressed, there will be
            % an extra '[1s' from a confirmed keystroke in the beginning
            for m = 1:5
                % reads a line out (may need to add in a loop using regexp to match
                % the '[x;1f' part), but could do...
                %if strcmp(response{m}(1), '[') && strcmp(response{m}(3:5), ';1f')
                % only need to implement this if we get cross-talk between pressing
                % buttons (it responds with '[0q' when you press something)
                data{m} = fscanf(serialObject);

                % if response is empty, error
                if isempty(data{m})
                    % errors
                    error('No data could be collected.')
                end

                % strips off the <ESC> from the end
                data{m} = data{m}(1:end - 1);
            end

        elseif strcmp(type, 'readnoflush')
            % this is designed to be used just after a keypress is done (returns
            % '[1s' if successful)) - if in a submenu, no update values are read
            % out

            % leaves a short pause to allow the buffer to fill with all of the
            % information - otherwise information could be missed (this has not
            % been tested - it may need to be extended, or may possibly even by
            % shortened)
            pause(0.5)

            % only read out data if there is some to be read out
            if ~serialObject.BytesAvailable
                % reads out everything in the buffer
                data = fread(serialObject, serialObject.BytesAvailable, '*char');

                % separates it out using <ESC> as a delimiter
                data = textscan(data, '%s', 'delimiter', char(27));

                % gets at most the last 4 lines out
                data = data{1}(max([end - 3, 1]):end);
            end
        end

        % pre-allocates the answer
        response = cell(4, 1);

        % reorders lines into order
        for m = 1:4
            % finds the line which is '[m;1' - this returns a cell array with
            % a 1 for the correct line, and [] for the wrong line - so can't
            % use simple logical indexing
            index = strfind(data, sprintf('[%d;1f',m));

            % extracts the correct index out
            for mm = 1:numel(index)
                % checks if it is not empty - it is therefore 1 otherwise
                if ~isempty(index{mm})
                    % assigns the line
                    response{m} = data{mm};

                    % leaves the loop
                    break
                end
            end
        end

        % strips out the first 5 characters of each line (the '[x;1f' part)
        for n = 1:numel(response)
            % if the line is not empty...
            if ~isempty(response{n})
                % changes the line
                response{n} = response{n}(6:end);
            end
        end
    end
end

Contact us