classdef TgtConnMgr < hgsetget
% TGTCONNMGR - Target Connectivity defines an infrastructure for connecting
% a host-based application (such as Simulink or an HMI) to a running target
% executable (such as a Rapid Accelerator simulation) through a set of
% services built into the generated code.  Application services provide
% various capabilities (such as uploading signals or tuning parameters) by
% transmitting data from host-to-target or target-to-host over a Communication
% service (such as TCP/IP).  Each Application service consists of a host-based
% module interfacing to the host-based application and a target-based module
% built into the generated code.  Together, these two modules represent the
% Application service and define a protocol for transferring data through the
% Communication service.
% 
% TgtConnMgr controls which Application and Communication services (if any)
% are added to the generated code during the build process.  Each Application
% service implements a MATLAB-based MCOS class derived from TgtConnAppSvc.
% Each supported code generation target (e.g. Rapid Accelerator) implements
% a MATLAB-based MCOS class derived from TgtConnTargetInfo whose class name
% is hard-coded in TgtConnMgr (such as RaccelConnection which is defined in
% coder.internal.connectivity.RaccelConnection).  Classes derived from
% TgtConnTargetInfo provide a TargetConnectionOptions structure detailing
% the Communication service to be used and a list of all Application services
% supported by the code generation target.  At build-time, TgtConnMgr is a
% singleton object (because only one model is built at a time) accessed through
% a set of static methods comprising the BUILD-TIME Public Interface.  There is
% no direct access whatsoever to the underlying TgtConnMgr object from the
% build process.  Each class derived from TgtConnTargetInfo is instantiated by
% TgtConnMgr and the first object whose isNeeded() method returns true is saved
% throughout the build process (all others are destroyed).  There can be only
% one Target Information in use at build-time.  Each Application service
% supported by the Target Information is instantiated and decides for itself
% whether or not it is needed in the generated code; the Application service
% object is destroyed if not needed, otherwise it is saved by TgtConnMgr and
% accessed throughout the build process at appropriate times so that all
% necessary Application services are properly built into the generated code.
% At the end of the build process the TgtConnMgr singleton object is written
% out to disk as a MAT-File in the codegen folder.
%
% At run-time, host-based applications only need to know the folder where
% code was generated (the model is not required) in order to load and access
% TgtConnMgr through the RUN-TIME Public Interface.  The host-based application
% may load multiple TgtConnMgr objects simultaneously, each one encapsulating
% information about a particular model build (i.e. TgtConnMgr is no longer a
% singleton object at run-time).  In this way, a host-based application may
% connect to multiple running targets simultaneously.  The TgtConnMgr RUN-TIME
% Public Interface allows a host-based application to start and stop all
% Application services built into the generated code and establish a connection
% with the running target.  Each Application service derived from TgtConnAppSvc
% has access to the TgtConnMgr at run-time and may obtain the TargetConnection
% object to communicate with the running target.
%

% Copyright 2014-2019 The MathWorks, Inc.
    
    %****************************
    %*
    %*        BUILD-TIME
    %*     Public Interface
    %* -------------------------
    %*
    %*  createInstance(topMdl, topMdlBuildArgs)
    %*  destroyInstance()
    %*  isNeeded()
    %*  isServiceNeeded(svcClassName)
    %*  getTopModelAndBuildArgs()
    %*  getMLSysLibPathAndExt()
    %*  setupBeforeTLC(mdl)
    %*  cleanupAfterTLC(mdl)
    %*  getIncludesAndDefinesCode()
    %*  getBackgroundTaskCode()
    %*  getMdlInitCode()
    %*  getMdlTermCode()
    %*  getPreStepCode()
    %*  getPostStepCode()
    %*  save()
    %*
    %****************************
        
    methods(Access=public,Static)
        % Manages a persistent variable holding the only instance of TgtConnMgr
        % which is valid only during the build process (empty otherwise).  The
        % start of the build process instantiates and caches the TgtConnMgr
        % object (via createInstance), the end of the build process resets and
        % clears the TgtConnMgr object (via destroyInstance).
        % 
        function createInstance(topMdl, topMdlBuildArgs, varargin)
            if ~isempty(coder.internal.connectivity.TgtConnMgr.getInstance())
                coder.internal.connectivity.TgtConnMgr.destroyInstance();
            end

            % Construct TgtConnMgr
            %
            obj = coder.internal.connectivity.TgtConnMgr(...
                coder.internal.connectivity.TgtConnMgr.convertToMdlName(topMdl),...
                topMdlBuildArgs);
            
            % Save as the build-time Singleton
            %
            coder.internal.connectivity.TgtConnMgr.manageInstance('set', obj);
            
            % Determine which services are required.  This must be called after
            % the Singleton instance is saved because each individual service
            % may call into the Singleton TgtConnMgr to get the 'topMdlName'
            % and 'topMdlBuildArgs' properties.  Therefore, a constructed
            % TgtConnMgr with these properties must be available before
            % attempting to instantiate any of the services.
            %
            obj.initialize(varargin{:});
        end
        function destroyInstance()
            coder.internal.connectivity.TgtConnMgr.manageInstance('set', []);
        end

        % Returns true if Target Connectivity is needed, false otherwise.
        % Target Connectivity is needed if the TgtConnMgr object has been
        % instantiated by the build process AND any services are required.
        % 
        function isNeeded = isNeeded()
            isNeeded = false; %assume
            
            obj = coder.internal.connectivity.TgtConnMgr.getInstance();
            if isempty(obj), return; end

            isNeeded = ~isempty(obj.appSvcMap) && ~isempty(obj.targetInfo);
        end
        
        % Returns true if a particular service is needed, false otherwise.
        % A particular service is needed if the TgtConnMgr object has been
        % instantiated by the build process AND the service's isNeeded()
        % method returns true.
        %
        function isNeeded = isServiceNeeded(svcClassName)
            isNeeded = false; %assume
            
            obj = coder.internal.connectivity.TgtConnMgr.getInstance();
            if isempty(obj), return; end

            isNeeded = obj.appSvcMap.isKey(svcClassName);
        end
    
        % Returns the top model name and top model build arguments.
        %
        function [topMdlName, topMdlBuildArgs] = getTopModelAndBuildArgs()
            topMdlName      = []; %assume
            topMdlBuildArgs = []; %assume
            
            obj = coder.internal.connectivity.TgtConnMgr.getInstance();
            if isempty(obj), return; end

            topMdlName      = obj.topMdlName;
            topMdlBuildArgs = obj.topMdlBuildArgs;
        end
    
        % Returns the MATLAB system library path and extension (e.g. ".lib")
        %
        function [libPath, libExt] = getMLSysLibPathAndExt()
            libPath = ''; %assume
            libExt  = ''; %assume
            
            obj = coder.internal.connectivity.TgtConnMgr.getInstance();
            if isempty(obj), return; end

            libPath = obj.libPath;
            libExt  = obj.libExt;
        end
    
        % Methods called by the build process allowing each service to perform
        % any initialization and cleanup before and after generating code for
        % each model in the model hierarchy, including adding compile info to
        % BuildInfo.  No actual code generation occurs in these functions.  To
        % insert custom code at various points in the generated code, use the
        % functions:  getIncludesAndDefinesCode(), getBackgroundTaskCode(),
        % getMdlInitCode(), getMdlTermCode(), getPreStepCode(), and
        % getPostStepCode().
        %
        function setupBeforeTLC(mdl)
            obj = coder.internal.connectivity.TgtConnMgr.getInstance();
            if isempty(obj), return; end
            
            mdlName = coder.internal.connectivity.TgtConnMgr.convertToMdlName(mdl);
            
            % The TgtConnMgr object is saved to a MAT-File in the top-model
            % build directory at the end of the build process, so any existing
            % file from a previous build is always deleted.
            %
            if strcmp(mdlName, obj.topMdlName) % only do this for top models, not sub models
                buildDirStruct = RTW.getBuildDir(obj.topMdlName);
                [fname, fext] = coder.internal.connectivity.TgtConnMgr.getFullFileNameForSavedTgtConnMgrFile( ...
                    obj.topMdlName, ...
                    buildDirStruct.BuildDirectory);
                
                if exist([fname fext], 'file')
                    delete([fname fext]);
                end
            end
            
            if coder.internal.connectivity.TgtConnMgr.isNeeded()
                % Allow each code generation target to perform any necessary setup.
                %
                % Model Reference SIM targets are built properly for a
                % host-based platform regardless of the particular code
                % generation target, so there is no need to call
                % setupBeforeTLC for the TargetInfo object.  Most targets
                % would need to special case the Model Reference SIM target
                % because it is built for a different platform (host-based
                % shared library) than the code generation target (e.g. SLRT).
                % Therefore, to avoid each different target platform special
                % casing the Model Reference SIM target, we skip the call
                % to setupBeforeTLC.
                %
                if ~strcmp(get_param(mdl, 'ModelReferenceTargetType'), 'SIM')
                    obj.targetInfo.setupBeforeTLC(mdlName);
                end
                
                % Allow each Application service to perform any necessary setup.
                %
                cellfun(@(svc) feval('setupBeforeTLC', svc, mdlName), obj.appSvcMap.values());
            end
        end
        function cleanupAfterTLC(mdl)
            obj = coder.internal.connectivity.TgtConnMgr.getInstance();
            if isempty(obj), return; end
            
            if coder.internal.connectivity.TgtConnMgr.isNeeded()
                mdlName = coder.internal.connectivity.TgtConnMgr.convertToMdlName(mdl);
            
                % Allow each code generation target to perform any necessary cleanup.
                %
                obj.targetInfo.cleanupAfterTLC(mdlName);
                
                % Allow each Application service to perform any necessary cleanup.
                %
                cellfun(@(svc) feval('cleanupAfterTLC', svc, mdlName), obj.appSvcMap.values());
            end
        end
        
        % Methods called by the build process from TLC only if Target
        % Connectivity is needed allowing each service to customize code
        % generation as needed.
        %
        function codeStr = getIncludesAndDefinesCode()
            codeStr = coder.internal.connectivity.TgtConnMgr.getCodeStrFromEachService('getIncludesAndDefinesCode');
        end
        function codeStr = getBackgroundTaskCode()
            codeStr = coder.internal.connectivity.TgtConnMgr.getCodeStrFromEachService('getBackgroundTaskCode');
        end
        function codeStr = getMdlInitCode()
            codeStr = coder.internal.connectivity.TgtConnMgr.getCodeStrFromEachService('getMdlInitCode');
        end
        function codeStr = getMdlTermCode()
            codeStr = coder.internal.connectivity.TgtConnMgr.getCodeStrFromEachService('getMdlTermCode');
        end
        function codeStr = getPreStepCode()
            codeStr = coder.internal.connectivity.TgtConnMgr.getCodeStrFromEachService('getPreStepCode');
        end
        function codeStr = getPostStepCode()
            codeStr = coder.internal.connectivity.TgtConnMgr.getCodeStrFromEachService('getPostStepCode');
        end
        
        % Method called at the end of the build process to serialize the
        % TgtConnMgr object to a MAT-File.  The build-time TgtConnMgr object
        % is needed at run-time to start and stop all of the services built
        % into the target executable during the build process.  A host
        % application need not have the model available at run-time, but it
        % must know the build directory where the target code was generated
        % to get access to the TgtConnMgr saved away at build-time.  The
        % host application can then call the start/stop methods at the
        % appropriate times to initiate and terminate a connection to the
        % running target.
        %
        function save()
            if coder.internal.connectivity.TgtConnMgr.isNeeded()
                obj = coder.internal.connectivity.TgtConnMgr.getInstance();
                if isempty(obj), return; end
                
                buildDirStruct = RTW.getBuildDir(obj.topMdlName);
                [fname, ~] = coder.internal.connectivity.TgtConnMgr.getFullFileNameForSavedTgtConnMgrFile( ...
                    obj.topMdlName, ...
                    buildDirStruct.BuildDirectory);
                
                path = fileparts(fname);
                rtwprivate('rtw_create_directory_path', path);
                
                save(fname, 'obj');
            end
        end
    end
    
    %****************************
    %*        BUILD-TIME
    %*     Private Interface
    %****************************
    
    % Complete list of all code generation targets recognized by the Target
    % Connectivity Manager.  This list must be updated any time support for
    % a new code generation target is added.
    %
    properties(Access=private,Constant)
        targetInfoClassNames = ...
            { ...
                'coder.internal.connectivity.RaccelConnection' ...
                'coder.internal.connectivity.SLRTConnection' ...
                'coder.internal.connectivity.SLDRTConnection' ...
                'coder.internal.connectivity.CoderTargetConnection' ...
            };
    end
    
    properties(Access=private)
        topMdlName;     % Top model performing the build
        topMdlBuildArgs % Build args for top model
        
        targetInfo;     % Only one target/Communication service allowed
        appSvcMap;      % All needed Application services
    
        libPath;        % MATLAB system library path
        libExt;         % Library extension (e.g. ".lib")
    end
    
    methods(Access=private)
        % Private constructor only called by the build process infrastructure via
        % static TgtConnMgr.createInstance().  At build time, a single TgtConnMgr
        % object is constructed and saved in the TgtConnMgr class.  The build
        % process accesses the singleton object via static class methods.  The
        % singleton is only valid during the build process and contains all of
        % the service objects needed for this particular build.
        %
        function obj = TgtConnMgr(topMdlName, topMdlBuildArgs)
            obj.topMdlName      = topMdlName;
            obj.topMdlBuildArgs = obj.getRequiredTopModelBuildArgs(topMdlBuildArgs);
        end
        function initialize(obj, varargin)
            obj.targetInfo = [];
            obj.appSvcMap  = containers.Map('KeyType', 'char', 'ValueType', 'any');
            
            % Determine the MATLAB system library path and extension.
            %
            if ispc
                if length(varargin)>=1
                    comp = varargin{1};
                else
                    comp = coder.make.internal.getMexCompilerInfo();
                end
                
                if ~isempty(comp) && strcmp(comp.toolChain, 'mingw64')
                    obj.libPath = fullfile(matlabroot,'extern','lib',computer('arch'),'mingw64');
                else
                    obj.libPath = fullfile(matlabroot,'extern','lib',computer('arch'),'microsoft');
                end
            else
                obj.libPath = fullfile(matlabroot,'bin',computer('arch'));
            end
            fullPathCommSvcLibWithNoExt = fullfile(obj.libPath, 'libmwsimulink');
            allfiles = dir([fullPathCommSvcLibWithNoExt '*']);
            [~,~,obj.libExt] = fileparts(allfiles(1).name);
            
            % Instantiate each Target Information class found on the MATLAB
            % path and save the first one needed.
            %
            for i=1:length(coder.internal.connectivity.TgtConnMgr.targetInfoClassNames)
                targetInfoClassName = coder.internal.connectivity.TgtConnMgr.targetInfoClassNames{i};
                if exist(targetInfoClassName, 'class')
                    tgtInfo = feval(targetInfoClassName);
                    if tgtInfo.isNeeded()
                        obj.targetInfo = tgtInfo;
                        break;
                    end
                end
            end
            
            % Instantiate each Application service class found on the MATLAB
            % path and save only those that are needed.
            %
            if ~isempty(obj.targetInfo)
                appSvcClassNames = obj.targetInfo.getSupportedAppSvcNames();
                for i=1:length(appSvcClassNames)
                    appSvcClassName = appSvcClassNames{i};
                    if exist(appSvcClassName, 'class')
                        svc = feval(appSvcClassName);
                        if svc.isNeeded()
                            obj.appSvcMap(appSvcClassName) = svc;
                        end
                    end
                end
            end
            
            % If no Application services are needed then the Target Information
            % is not needed.
            %
            if isempty(obj.appSvcMap)
                obj.targetInfo = [];
            end
        end
    end
    
    methods(Access=private,Static)
        function codeStr = getCodeStrFromEachService(func)
            codeStr = ' '; % return whitespace (not empty) by default for TLC
            
            obj = coder.internal.connectivity.TgtConnMgr.getInstance();
            if isempty(obj), return; end
            
            codeStr = [codeStr feval(func, obj.targetInfo)];
                        
            retVals = cellfun(@(svc) feval(func, svc), obj.appSvcMap.values(), 'UniformOutput', false);
            if any(cellfun(@(str) ~isempty(str), retVals))
                codeStr = [codeStr cell2mat(retVals)];
            end
        end
        
        %
        % Manage the TgtConnMgr singleton object during the build process.
        %
        function obj = getInstance()
            obj = coder.internal.connectivity.TgtConnMgr.manageInstance('get');
        end
        function varargout = manageInstance(command, varargin)
            mlock;
            persistent theInstance;
            switch(command)
              case 'get'
                varargout{1} = theInstance;
              case 'set'
                theInstance = varargin{1};
              otherwise
                assert(false);
            end
        end
        
        function requiredBuildArgs = getRequiredTopModelBuildArgs(topMdlBuildArgs)
           % g2208250 Only a small subset of build args are required for
           % Target Connectivity. To avoid issues serializing the entire
           % class, create a struct containing only the required fields.
            requiredBuildArgs.ModelReferenceTargetType = topMdlBuildArgs.ModelReferenceTargetType;
            requiredBuildArgs.IsRapidAccelerator = topMdlBuildArgs.IsRapidAccelerator;
        end
    end
    
    %****************************
    %*
    %*         RUN-TIME
    %*     Public Interface
    %* -------------------------
    %*
    %*  load(topMdl, topMdlBuildDir)
    %*  start(obj, varargin)
    %*  stop(obj)
    %*  getService(obj, svcClassName)
    %*  getTargetConnection(obj)
    %*
    %****************************
    
    methods(Access=public,Static)
        % Method called by host applications to load the TgtConnMgr object
        % from a a MAT-File saved during the build process.  The build-time
        % TgtConnMgr object is needed at run-time to start and stop all of
        % the services added to the target executable during the build
        % process.  A host application need not have the model available,
        % but it must know the build directory where the target code was
        % generated to load the MAT-File and get the TgtConnMgr object
        % (which may be empty if Target Connectivity was not required at
        % build-time).
        %
        function tgtConnMgrObj = load(topMdl, topMdlBuildDir)
            tgtConnMgrObj = [];
            [fullFileName, fileExt] = coder.internal.connectivity.TgtConnMgr.getFullFileNameForSavedTgtConnMgrFile(topMdl, topMdlBuildDir);
            if exist([fullFileName fileExt], 'file')
                tgtconn = load(fullFileName);
                tgtConnMgrObj = tgtconn.obj; % Was saved with 'obj' as variable name
            end
        end
    end
        
    methods(Access=public)
        % Methods called at run-time by a host application after reading
        % the TgtConnMgr object from a MAT-File in the build directory.
        % Additional arguments meant for individual services may be passed
        % in as name-value pairs through varargin.
        %
        function start(obj, varargin)
            %
            % Collect all run-time arguments.
            %
            argMap = containers.Map('KeyType', 'char', 'ValueType', 'any');
            for nArg = 1:2:length(varargin)
                argMap(varargin{nArg}) = varargin{nArg+1};
            end
            
            %
            % Initiate connection to target.
            %
            tgtConnOpts = obj.targetInfo.getTargetConnectionOptions(argMap);
            obj.targetConnection = coder.internal.connectivity.TargetConnection(tgtConnOpts);

            %
            % Wait until connection is established.
            %
            count = 1;
            maxCount = 100;
            pauseValInSecs = 0.1;
            while (~obj.targetConnection.isConnected())
                if count >= maxCount
                    errId = 'Connectivity:tgtconn:ErrorConnectTimeout';
                    exc = MException(errId, DAStudio.message(errId, maxCount*pauseValInSecs));
                    throw(exc);
                end
                count = count+1;
                pause(pauseValInSecs);
            end
            
            %
            % Start each individual Application service.
            %
            appSvcs = obj.appSvcMap.values();
            for i=1:length(appSvcs)
                appSvcs{i}.tgtConnMgr = obj;
                appSvcs{i}.start(argMap);
            end
        end
        function stop(obj)
            %
            % Terminate each individual Application service.
            %
            appSvcs = obj.appSvcMap.values();
            for i=1:length(appSvcs)
                appSvcs{i}.stop();
                appSvcs{i}.tgtConnMgr = [];
            end
            
            %
            % Terminate connection to target.
            %
            obj.targetConnection = [];
        end
        
        % Returns a particular service if added in this particular run-time
        % instance of TgtConnMgr.
        %        
        function service = getService(obj, svcClassName)
            service = [];
            if obj.appSvcMap.isKey(svcClassName)
                service = obj.appSvcMap(svcClassName);
            end
        end        
        
        % Returns the target connection object for Application and
        % Communication services to communicate with the running target.
        %
        function val = getTargetConnection(obj)
            val = obj.targetConnection;
        end
    end
    
    %****************************
    %*         RUN-TIME
    %*     Private Interface
    %****************************
    
    properties(Access=private)
        targetConnection; % Controls connection to target
    end
    
    %****************************
    %*    HELPER FUNCTIONS
    %****************************
    
    methods(Access=private,Static)
        function [fullFileName, fileExt] = getFullFileNameForSavedTgtConnMgrFile(mdl, buildDir)
            mdlName = coder.internal.connectivity.TgtConnMgr.convertToMdlName(mdl);
            fileName = [mdlName '_TgtConnMgr'];
            fileExt  = '.mat';
            fullFileName = fullfile(buildDir,'tmwinternal',fileName);
        end
    
        function name = convertToMdlName(mdl)
            % The input argument 'mdl' may be a name or a handle, convert it
            % into a model name.
            %
            if (nnz(ishandle(mdl)) == 0)
                name = mdl;
            else
                name = getfullname(mdl);
            end
        end
    end        
end
