classdef Robolink < handle
% This class allows to create macros for Robodk program.
% Any interaction is made through "items". An item is an object in the
% Robodk tree (it can be either a robot, an object, a tool, a frame, a
% program, ...).
% An item is a unique number (pointer) that represents one object.
%
% RoboDK api Help:
% ->Type "doc Robolink"            for more help on the Robolink class
% ->Type "doc RobolinkItem"        for more help on the RobolinkItem item class
% ->Type "showdemo Example_RoboDK" for examples on how to use RoboDK's API using the last two classes

    properties
        APPLICATION_DIR = 'C:\RoboDK\bin\RoboDK.exe'; % file path to the Robodk program (executable)
        COM = 0;         % tcpip com
    end
    properties(GetAccess = 'private', SetAccess = 'private')
        SAFE_MODE = 1;   % checks that provided items exist in memory
        AUTO_UPDATE = 0; % if AUTO_UPDATE is zero, the scene is rendered after every function call
        TIMEOUT = 5;     % timeout for communication, in seconds
        PORT_START = 20500; % port to start looking for app connection
        PORT_END   = 20510; % port to stop looking for app connection
        PORT = -1;
    end
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    methods % (Access = 'private')
        function connected = is_connected(this)
            % This is a private function.
            % Returns 1 if connection is valid, returns 0 if connection is invalid
            if strcmp(this.COM,'tcpip') ~= 0
                connected = 0;
                return
            end
            status = get(this.COM,'status');
            if status(1) == 'o'
                connected = 1;
            end
        end
        function check_connection(this)
            % This is a private function.
            % If we are not connected it will attempt a connection, if it fails, it will throw an error
            if ~is_connected(this) && Connect(this) ~= 1
                error('Unable to connect');
            end
            flushinput(this.COM);%Clear anything still in the input buffer from the previous commands
            flushoutput(this.COM);%Remove data from output buffer.
        end
        function status = check_status(this)
            % This is a private function.
            % this function checks the status of the connection
            lastwarn(''); % makes timeout warning an error
            status = fread(this.COM, 1, 'int32');
            if ~isempty(lastwarn)
                error(lastwarn)
            end
            if status > 0 && status < 10
                strproblems = 'Unknown error';
                switch status
                    case 1
                        strproblems = 'Invalid item: The item identifier provided is not valid or it does not exist.';
                    case 2
                        strproblems = rec_line(this);
                        warning(strproblems);
                        status = 0;
                        return
                        %error(strproblems);
                        %status = -1;
                        %return
                    case 3
                        strproblems = rec_line(this);
                    case 9
                        strproblems = 'Invalid license. Contact us at: www.robodk.org';
                    otherwise
                        % do nothing
                end
                fprintf([strproblems,'\n']);
                error(strproblems);
            elseif status == 0
                % everything is OK
            else
                error('problems running function');
                fclose(this.COM);% reconnect to reset connection
                fopen(this.COM);
                validate_connection(this);
            end
        end
        function send_line(this, string)
            % This is a private function.
            % Sends a string of characters with a \n
            fprintf(this.COM, string);
        end
        function string = rec_line(this)
            % This is a private function.
            % Receives a string. It reads until if finds \n
            string = [];
            chari = fread(this.COM, 1, 'char');
            while chari ~= 10 %LF
                string = [string, chari];
                chari = fread(this.COM, 1, 'char');
            end
            string = char(string);
        end
        function send_item(this, item)
            % This is a private function.
            % Sends an item pointer
            fwrite(this.COM, item.item(1), 'float64');
        end
        function item = rec_item(this)
            % This is a private function.
            % Receives an item pointer
            itemnum = fread(this.COM, 1, 'float64');
            item = RobolinkItem(this, itemnum);
        end
        function send_pose(this, pose)
            % This is a private function.
            % Sends a pose (4x4 matrix)
            fwrite(this.COM, pose(1:16), 'double');
        end
        function pose = rec_pose(this)
            % This is a private function.
            % Receives a pose (4x4 matrix)
            pose = fread(this.COM, 16, 'double');
            pose = reshape(pose,4,4);
        end
        function send_xyz(this, pos)
            % This is a private function.
            % Sends an xyz vector
            fwrite(this.COM, pos(1:3), 'double');
        end
        function pos = rec_xyz(this)
            % This is a private function.
            % Receives an xyz vector (3x1 matrix)
            pos = fread(this.COM, 3, 'double');
            pos = reshape(pos,3,1);
        end
        function send_int(this, num)
            % This is a private function.
            % Sends an int (32 bits)
            if numel(num) > 1
                num = num(1);
            end
            fwrite(this.COM, num, 'int32');
        end
        function num = rec_int(this)
            % This is a private function.
            % Receives an int (32 bits)
            num = fread(this.COM, 1, 'int32');
        end
        function send_array(this, values)
            % This is a private function.
            % Sends an array of doubles
            nval = numel(values);
            send_int(this, nval);
            if nval > 0
                valok = reshape(values,nval,1);
                fwrite(this.COM, valok, 'double');
            end
        end
        function values = rec_array(this)
            % This is a private function.
            % Receives an array of doubles
            nvalues = rec_int(this);
            if nvalues > 0
                values = fread(this.COM, nvalues, 'double');
            else
                values = [];
            end
        end
        function send_matrix(this, mat)
            % This is a private function.
            % Sends a 2 dimensional matrix (nxm)
            size1 = size(mat,1);
            size2 = size(mat,2);
            send_int(this,size1);
            send_int(this,size2);
            for j=1:size2
                for i=1:size1
                    fwrite(this.COM,mat(i,j),'float64');
                end
            end
        end
        function mat = rec_matrix(this)
            % This is a private function.
            % Receives a 2 dimensional matrix (nxm)
            size1 = rec_int(this);
            size2 = rec_int(this);
            if size1*size2 > 0
                mat = fread(this.COM, size1*size2, 'double');
                mat = reshape(mat, size2, size1)';
            else
                mat = [];
            end
        end
        function moveX(this, target, itemrobot, movetype, blocking)
            % This is a private function.
            % Performs a linear or joint movement. Use MoveJ or MoveL instead.
            if nargin < 5
                blocking = 300;
            end
            %check_connection(this);
            itemrobot.WaitMove();% checks connection
            command = 'MoveX';
            send_line(this, command);
            send_int(this, movetype);
            if numel(target) == 1 % target is an item
                send_int(this,3);
                send_array(this,[]);
                send_item(this,target);
            elseif size(target,1) == 1 || size(target,2) == 1 % target are joints
                send_int(this, 1);
                send_array(this, target);
                send_item(this, RobolinkItem(this, 0));
            elseif numel(target) == 16 % target is a pose
                send_int(this, 2);
                send_array(this, target);
                send_item(this, RobolinkItem(this, 0));
            else
                error('Invalid input values');
            end
            send_item(this, itemrobot);
            check_status(this);
            if blocking
                itemrobot.WaitMove();
            end
        end
    end
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    methods
        function this = Robolink()
            % Creates a connection to the RoboDK's API
            Connect(this);
        end
        function ok = Set_connection_params(this, safe_mode, auto_update, timeout)
            % Sets the API behavior parameters: SAFE_MODE, AUTO_UPDATE and TIMEOUT.
            % SAFE_MODE checks that item pointers provided by the user are valid.
            % AUTO_UPDATE checks that item pointers provided by the user are valid.
            % TIMEOUT is the timeout to wait for a response. Increase if you experience problems loading big files.
            % If connection failed returns 0.
            % Example:  Set_connection_params(0,0); % Use for speed. Render() must be called to refresh the window.
            %           Set_connection_params(1,1); % Default behavior. Updates every time.
            % In  1 (optional) : int -> SAFE_MODE (1=yes, 0=no)
            % In  2 (optional) : int -> AUTO_UPDATE (1=yes, 0=no)
            % In  3 (optional) : int -> TIMEOUT (1=yes, 0=no)
            % Out 1 : int -> connection status (1=ok, 0=problems)
            if nargin > 1
                this.SAFE_MODE = safe_mode;
            end
            if nargin > 2
                this.AUTO_UPDATE = auto_update;
            end
            if nargin > 3
                this.TIMEOUT = timeout;
            end
            fprintf(this.COM, 'CMD_START'); % appends LF (char(10))
            fprintf(this.COM, sprintf('%i %i', this.SAFE_MODE, this.AUTO_UPDATE));% appends LF
            response = rec_line(this);
            if strcmp(response, 'READY')
                ok = 1;
            else
                ok = 0;
            end
        end
        function connected = Connect(this)
            % Establishes a connection with Robodk. RoboDK must be running, otherwise, the variable APPLICATION_DIR must be set properly.
            % If the connection succeededs it returns 1, otherwise it returns 0
            connected = 0;
            for i=1:2
                for port=this.PORT_START:this.PORT_END
                    this.COM = tcpip('localhost', port, 'Timeout', this.TIMEOUT, 'BytesAvailableFcnMode', 'byte', 'InputBufferSize', 4000);
                    try
                        fopen(this.COM);
                        connected = is_connected(this);
                        if connected
                            break
                        end
                    catch

                    end
                end
                if connected % if status is closed, try to open application
                    this.PORT = port;
                    break;
                else
                    try
                        winopen(this.APPLICATION_DIR);
                        pause(2);
                    catch
                        error(['application path is not correct or could not start: ',this.APPLICATION_DIR]);
                    end
                end
            end
            if connected && ~Set_connection_params(this)
                connected = 0;
            end
        end
    end
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    methods
        function item = Item(this, name)
            % Returns an item by its name. If there is no exact match it will return the last closest match.
            % Example: item = Item('Robot');
            check_connection(this);
            command = 'G_Item';
            send_line(this, command);
            send_line(this, name);
            item = rec_item(this);        %     item = fread(com, 2, 'ulong');% ulong is 32 bits!!!
            check_status(this);
        end
        function list = ItemList(this)
            % Returns a list of names of all available items in the currently open station in Robodk.
            check_connection(this);
            command = 'G_List_Items';
            send_line(this, command);
            count = rec_int(this);
            list = cell(1,count);
            for i=1:count
                namei = rec_line(this);
                fprintf('%i\t->\t%s\n',i,namei);
                list{i} = namei;
            end
            check_status(this);
        end
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function Copy(this, item)
            % Makes a copy of an item (same as Ctrl+C), which can be pasted (Ctrl+V) using Paste_Item().
            % In 1 : item (optional) -> Parent item
            % Example:  object = RL.Item('My Object');
            %           RL.Copy(object);
            %           newobject = RL.Paste();
            %           newobject.setName(newobject, 'My Object (copy 1)');
            %           newobject = Paste();
            %           newobject.setName(newobject, 'My Object (copy 2)');
            if nargin < 2
                item = RobolinkItem(this);
            end
            check_connection(this);
            command = 'Copy';
            send_line(this, command);
            send_item(this, item);
            check_status(this);
        end
        function newitem = Paste(this, toparent)
            % Pastes the copied item (same as Ctrl+V). Needs to be used after Copy_Item(). See Copy() for an example.
            % In 1 : item
            if nargin < 2
                toparent = RobolinkItem(this);
            end
            check_connection(this);
            command = 'Paste';
            send_line(this, command);
            send_item(this, toparent);
            newitem = rec_item(this);
            check_status(this);
        end
        function newitem = AddFile(this, filename, parent)
            % Loads a file and attaches it to parent. It can be any file supported by Robodk.
            % Timeout may have to be increased if big files are loaded.
            % In 1  : string -> absolute path of the file
            % In 2 (optional) : item -> parent to attach
            % Out 1 : item -> added item (0 if failed)
            check_connection(this);
            if nargin < 3
                parent = RobolinkItem(this);
            end
            command = 'Add';
            send_line(this, command);
            send_line(this, filename);
            send_item(this, parent);
            newitem = rec_item(this);
            check_status(this);
        end
        function newitem = AddTarget(this, name, itemparent, itemrobot)
            % Adds a new target that can be reached with a robot.
            % In  1 : string -> name of the target
            % In  2 (optional) : item -> parent to attach to (such as a frame)
            % In  3 (optional) : item -> main robot that will be used to go to this target
            % Out 1 : item -> the new item created
            if nargin < 3
                itemparent = RobolinkItem(this);
            end
            if nargin < 4
                itemrobot = RobolinkItem(this);
            end
            check_connection(this);
            command = 'Add_TARGET';
            send_line(this, command);
            send_line(this, name);
            send_item(this, itemparent);
            send_item(this, itemrobot);
            newitem = rec_item(this);
            check_status(this);
        end
        function newitem = AddFrame(this, name, itemparent)
            % Adds a new Frame that can be referenced by a robot.
            % In  1 : string -> name of the frame
            % In  2 (optional) : item -> parent to attach to (such as the rrobot base frame)
            % Out 1 : item -> the new item created
            if nargin < 3
                itemparent = RobolinkItem(this);
            end
            check_connection(this);
            command = 'Add_FRAME';
            send_line(this, command);
            send_line(this, name);
            send_item(this, itemparent);
            newitem = rec_item(this);
            check_status(this);
        end
        function newitem = AddProgram(this, name, itemrobot)
            % Adds a new program.
            % In  1 : string -> name of the program
            % In  2 (optional) : item -> robot that will be used
            % Out 1 : item -> the new item created
            if nargin < 3
                itemrobot = RobolinkItem(this);
            end
            check_connection(this);
            command = 'Add_PROG';
            send_line(this, command);
            send_line(this, name);
            send_item(this, itemrobot);
            newitem = rec_item(this);
            check_status(this);
        end
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function prog_status = RunProgram(this, fcn_param)
            % Adds a function call in the program output. RoboDK will handle the syntax when the code is generated for a specific robot. If the program exists it will also run the program in simulate mode.
            % In  1 : fcn call -> string of the program to run
            % Out 1 : this function always returns 0"""
            prog_status = this.RunCode(fcn_param, 1);
        end
        function prog_status = RunCode(this, code, code_is_fcn_call)
            % Adds code to run in the program output. If the program exists it will also run the program in simulate mode.
            % In  1 : code -> string of the code or program to run
            % In  2 : code_is_fcn_call -> True if the code corresponds to a function call, if so, RoboDK will handle the syntax when the code is generated
            % Out 1 : this function alsways returns 0
            if nargin < 3
                code_is_fcn_call = 0;
            end
            check_connection(this);
            command = 'RunCode';
            send_line(this, command);
            send_int(this, code_is_fcn_call);
            send_line(this, strrep(strrep(code,'\r\n','<<br>>'),'\n','<<br>>'))
            prog_status = rec_int(this);
            check_status(this);
        end
        function prog_status = RunMessage(this, message, message_is_comment)
            % Shows a message or a comment in the robot program.
            % In  1 : string -> message or comment to show in the teach pendant
            % Out 1 : int -> if message_is_comment is set to True (or 1) the message will appear only as a comment in the code
            if nargin < 3
                message_is_comment = 0;
            end
            check_connection(this);
            command = 'RunMessage';
            send_line(this, command);
            send_int(this, message_is_comment);
            send_line(this, strrep(strrep(message,'\r\n','<<br>>'),'\n','<<br>>'))
            prog_status = rec_int(this);
            check_status(this);
        end
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function Render(this, always_render)
            % Renders the scene. This function turns off rendering unless always_render is set to true.
            if nargin < 2
                always_render = 0;
            end
            auto_render = int32(~always_render);
            check_connection(this);
            command = 'Render';
            send_line(this, command);
            send_int(this, auto_render);
            check_status(this);
        end
        function ncollisions = Collision(this)
            % Returns the number of pairs of objects that are currently in collision state.
            check_connection(this);
            command = 'Collision';
            send_line(this, command);
            ncollisions = rec_int(this);
            check_status(this);
        end
        function setSimulationSpeed(this, speed)
            % Sets the current simulation speed. Set the speed to 1 for a real-time simulation. The slowest speed allowed is 0.001 times the real speed.
            check_connection(this);
            command = 'SimulateSpeed';
            send_line(this, command);
            send_int(this, speed*1000);
            check_status(this);
        end
        function setRunMode(this, run_mode)
            % Sets the behavior of the script. By default, robodk shows the path simulation for movement instructions (run_mode=1).
            % Setting the run_mode to 2 allows to perform a quick check to see if the path is feasible.
            % In   1 : int = RUNMODE
            % RUNMODE_SIMULATE=1        performs the simulation moving the robot (default)
            % RUNMODE_QUICKVALIDATE=2   performs a quick check to validate the robot movements
            % RUNMODE_MAKE_ROBOTPROG=3  makes the robot program
            % RUNMODE_RUN_REAL=4        moves the real robot is it is connected
            if nargin < 2
                run_mode = 1;
            end
            check_connection(this);
            command = 'S_RunMode';
            send_line(this, command);
            send_int(this, run_mode);
            check_status(this);
        end
        function run_mode = RunMode(this)
            % Gets the behavior of the script. By default, robodk shows the path simulation for movement instructions (run_mode=1).
            % If run_mode = 2, the script is performing a quick check to see if the path is feasible (usually managed by the GUI).
            % If run_mode = 3, the script is generating the robot program (usually managed by the GUI).
            % Out  1 : int = RUNMODE
            % RUNMODE_SIMULATE=1        performs the simulation moving the robot (default)
            % RUNMODE_QUICKVALIDATE=2   performs a quick check to validate the robot movements
            % RUNMODE_MAKE_ROBOTPROG=3  makes the robot program
            % RUNMODE_RUN_REAL=4        moves the real robot is it is connected
            check_connection(this);
            command = 'G_RunMode';
            send_line(this, command);
            run_mode = rec_int(this);
            check_status(this);
        end
        function value = getParam(this, param)
            % Gets a global parameter from the RoboDK station.
            % In  1 : string = parameter
            % Out 1 : string = value
            % Available parameters:
            % PATH_OPENSTATION = folder path of the current .stn file
            % FILE_OPENSTATION = file path of the current .stn file
            % PATH_DESKTOP = folder path of the user's folder
            % PATH_LIBRARY = library path
            if nargin < 2
                param = 'PATH_OPENSTATION';
            end
            check_connection(this);
            command = 'G_Param';
            send_line(this, command);
            send_line(this, param);
            value = rec_line(this);
            check_status(this);
        end
        function ShowSequence(this, matrix)
            % Displays a sequence of joints
            % In  1 : joint sequence as a 6xN matrix or instruction sequence as a 7xN matrix
            RobolinkItem(this, 0).ShowSequence(matrix);
        end
        function xyz = LaserTracker_Measure(this, estimate, search)
            % Takes a laser tracker measurement with respect to the reference frame. If an estimate point is provided, the laser tracker will first move to those coordinates. If search is True, the tracker will search for a target.
            % Returns the XYZ coordinates of target if it was found. Othewise it retuns the vector [0;0;0].
            if nargin < 2
                estimate = [0; 0; 0];
            end
            if nargin < 3
                search = 0;
            end
            check_connection(this);
            command = 'MeasLT';
            send_line(this, command);
            send_xyz(this, estimate);
            send_int(this, search);
            xyz = rec_xyz(this);
            check_status(this);
        end
        function [collision, itempicked, xyz] = Collision_Line(this, p1, p2, ref)
            % Checks the collision between a line and the station. The line is composed by 2 points.
            % In  1 : p1 -> start point of the line
            % In  2 : p2 -> end point of the line
            % In  3 : pose (optional) -> reference of the 2 points
            % Out 1 : collision -> True if there is a collision, False otherwise
            % Out 2 : item -> Item collided
            % Out 3 : point -> collision point (station reference)
            if nargin < 4
                ref = eye(4);
            end
            p1abs = ref*[p1(1:3);1];
            p2abs = ref*[p2(1:3);1];
            check_connection(this);
            command = 'CollisionLine';
            send_line(this, command);
            send_xyz(this, p1abs);
            send_xyz(this, p2abs);
            itempicked = rec_item(this);
            xyz = rec_xyz(this);
            collision = itempicked.Valid();
            check_status(this);
        end
   end
end