classdef (Hidden) RtIOStream < handle
%RTIOSTREAM MATLAB-based RtIOStream communications interface for host. 
%
%   This is an undocumented class. Its methods and properties are likely to
%   change without warning from one release to the next.

%   Copyright 2011-2020 The MathWorks, Inc.
    
    properties (SetAccess = 'private', GetAccess = 'private')
       StreamID; % RtIOStream stream id
       
       OpenTimeout; % Open comms timeout (in secs)              
       
       RecvTimeout; % Recv comms timeout (in sec)
       SendTimeout; % Send comms timeout (in sec)
       
       IsByteInputOutputOnly logical; % Does the target only support byte input and output?
                                      % For example word addressable
                                      % targets may support word I/O
       
       IncludeHeaders; % Additional header files required to compile the rtiostream Sfunction
       
       PreOpenHooks cell; % Array of function handle hooks executed on preOpen
       PostOpenHooks cell; % Array of function handle hooks executed on prostOpen
    end        

    methods (Abstract, Access = 'protected')
        streamID = openHook(this, ioDataType); % open connection       
    end
    
    methods (Abstract)
        sizeSent = send(this, data); % send data
        data = recv(this, receiveSize); % receive data
        close(this); % close connection        
    end
   
    methods 
        function this = RtIOStream       
            this.StreamID = -1; % invalid stream id     
            this.OpenTimeout = 20; % default
            this.RecvTimeout = 60; % default
            this.SendTimeout = 10; % default
            this.IsByteInputOutputOnly = true;
        end
        
        % return the RtIOStream stream ID
        function streamID = getStreamID(this)
           streamID = this.StreamID; 
        end
        
        % return the open comms timeout
        function openTimeout = getOpenTimeout(this)
           openTimeout = this.OpenTimeout; 
        end
        
        % set the open comms timeout
        function setOpenTimeout(this, openTimeout)
           this.OpenTimeout = openTimeout; 
        end
        
        % get the send timeout
        function sendTimeout = getSendTimeout(this)
            sendTimeout = this.SendTimeout;
        end                                              
        
        % set the send timeout
        function setSendTimeout(this, sendTimeout)
            this.SendTimeout = sendTimeout;            
        end
           
        % get the recv timeout
        function recvTimeout = getRecvTimeout(this)
            recvTimeout = this.RecvTimeout;
        end                                              
        
        % set the recv timeout
        function setRecvTimeout(this, recvTimeout)
            this.RecvTimeout = recvTimeout;            
        end                       
           
        % get whether the comms are byte I/O only
        function isByteInputOutputOnly = getIsByteInputOutputOnly(this)
            isByteInputOutputOnly = this.supportsOnlyByteIO();
        end                                              
        
        % set whether the comms are byte I/O only
        function setIsByteInputOutputOnly(this, isByteInputOutputOnly)
            this.IsByteInputOutputOnly = isByteInputOutputOnly;            
        end   
        
        % get the include headers
        function includeHeaders = getIncludeHeaders(this)
            includeHeaders = this.IncludeHeaders;
        end                                              
        
        % set the include headers
        function setIncludeHeaders(this, includeHeaders)
            this.IncludeHeaders = includeHeaders;            
        end           
        
        % add the specified pre open hook to the pre open hooks 
        function addPreOpenHook(this, hook)
            this.PreOpenHooks{end+1} = hook;
        end
        
        % call pre open hooks
        function preOpen(this)
            cellfun(@feval, this.PreOpenHooks);
        end
        
        % add the specified post open hook to the pre open hooks 
        function addPostOpenHook(this, hook)
            this.PostOpenHooks{end+1} = hook;
        end
        
        % call post open hooks
        function postOpen(this)
            cellfun(@feval, this.PostOpenHooks);
        end            
        
        % open the RtIOStream channel
        function open(this, ioDataType)
            
            keepTrying = true;
            time_start = tic;
            while keepTrying
                this.StreamID = this.openHook(ioDataType);
                if (this.StreamID < 0) && (toc(time_start) <= this.getOpenTimeout)
                    % the call to this.openHook is not instantaneous when
                    % connecting to a wrong static IP address, the timeout must
                    % consider the time spent trying to open the connection.
                    pause(0.5);
                else
                    keepTrying = false;
                end
            end
            if ( this.StreamID <0 )
                error(message('Connectivity:target:ErrorOpeningChannel'));
            end            
        end
        
        % specifies whether the class supports only byte I/O or other types
        % of I/O data
        function supportsOnlyByteIO = supportsOnlyByteIO(this)
           % default is only byte I/O
           % 
           % override in subclass to alter
           supportsOnlyByteIO = this.IsByteInputOutputOnly; 
        end 
        
        % block until all data is sent
        function blockingSend(this, data)
            sendSize = length(data);
            sizeSent = this.send(data);
            if sizeSent<sendSize
                initialTime = tic;
                while(sizeSent<sendSize)
                    nextData = data(sizeSent+1:end);
                    nextSizeSent = this.send(nextData);
                    if nextSizeSent > 0
                        sizeSent = sizeSent+nextSizeSent;
                        % reset wait time
                        initialTime = tic;
                    else
                        % check for timeout
                        if toc(initialTime) > this.SendTimeout
                            msgObj = message('Connectivity:target:ErrorSendReceiveCauses');
                            errorCauses = msgObj.getString;
                            error(message('Connectivity:target:ErrorSendTimeout', uint32(this.SendTimeout), errorCauses));
                        end
                    end
                end
            end
        end
        
        % block until all data is received
        function data = blockingRecv(this, receiveSize)
            data = this.recv(receiveSize);
            sizeRecvd = length(data);            
            if sizeRecvd<receiveSize
                initialTime = tic;
                while(sizeRecvd<receiveSize)
                    newData = this.recv(receiveSize-sizeRecvd);
                    newSize = length(newData);
                    if newSize > 0
                        data(sizeRecvd+1:sizeRecvd+newSize)=newData(1:newSize);
                        sizeRecvd = sizeRecvd+newSize;
                        % reset wait time
                        initialTime = tic;
                    else
                        % check for timeout
                        if toc(initialTime) > this.RecvTimeout                                                   
                            msgObj = message('Connectivity:target:ErrorSendReceiveCauses');
                            errorCauses = msgObj.getString;
                            error(message('Connectivity:target:ErrorRecvTimeout', uint32(this.RecvTimeout), errorCauses));
                        end
                    end
                end
            end
        end
    end
end
