% This class creates a bridge between matlab and ros for controlling the
% AMBF simulation for the galen robot. The simulation will be controlled
% via class methods in found in the galen_interface objects.
% These functions will move the robot according to velocity, position, or
% effort commands defined in the set_control_type functions. This interface
% is only intended to control the robot in simulation.
% To use this class to control the robot,ensure ROS is running by running
% roscore in a terminal and then open the ambf model in a separate terminal
% Instructions on how to install the AMBF application and how to use the
% robot with ambf are found in:

% TOGAC25_AMBF_IN_AMBFforGalen.pdf

% Instructions for using matlab with ambf are found in:

% TOGAC29_AMBF_IN_ConnectMatlab2AMBF.pdf

% Modified 03-24-2023
% Created by Brevin Banks



classdef galen_interface < handle


    % These settings will not change after class construction
    properties (SetAccess = immutable)
        % Joint limits, speed limits, acceleration limits, home pose
        % information should go here

        joint_names = {...
            'carriage1_joint',...
            'carriage2_joint',...
            'elbow_joint',...
            'roll_joint',...
            'TiltDistalLinkageandForceSensor-Endoscope35degreeinREMS',...
            'tilt_joint'};

        children_names = {...
            'Carriage1',...
            'Carriage2',...
            'Carriage3',...
            'RollArmBase',...
            'Endoscope35degreeinREMS',...
            'TiltProximalLinkage'};
        % From AMBF ADF model
        Body_masses = [0 10 10 10 10 10 10 10 10 10 0 0.5 0.1 5 5 5 5 5 5 10 5 0.5 0.5 0.5 1];
        Body_names = {'Base','Carriage1','Carriage1_lh','Carriage1_rh', ...
            'Carriage2','Carriage2_lh','Carriage2_rh', ...
            'Carriage3','Carriage3_lh','Carriage3_rh', ...
            'Chassis', 'Endoscope 35 degree in REMS','Endoscope Tip', ...
            'Leg1_l','Leg1_r','Leg2_l','Leg2_r','Leg3_l','Leg3_r', ...
            'Mobile Platform','Roll Arm Base', 'Roll Arm Housing', ...
            'Tilt Distal Linkage and Force Sensor', 'Tilt Middle Linkage', ...
            'Tilt proximal Linkage'};


    end

    % values set by this class, can be read by others
    properties (SetAccess = protected)
        galen_joint_states   % Last received galen joint_states
        E35state
        % Admittance Properties
        m_ad
        b_ad

        % The sum of the lumped mass for 3 carriage identical joint control
        Mext_Lumped_mass = 95; % kg
        % Mass and friction of one of the three carriage joints
        Single_carriage_mass = 30; % kg
        carriage_kinetic_friction = 0.1;
        % The angle of the carriage leg to the platform of the wrist when
        % all 3 carriage joints are held at the same reference pose
        carriage_triad_steady_angle = 1.842; % in rad

        g = 9.81; % gravity constant


        %PID gains from the ADF model
        P = 11000;
        I = 110;
        D = 110;

        T = 1/200; % desired sample time

    end

    % only this class methods can view/modify
    properties (SetAccess = private)

        % subscribers
        galen_joint_states_sub
        E35_wrench_sub
        % Individual frame subs

        % publishers
        galen_joint_cmd_pub

        % messages
        galen_command_message

        % Properties from messages
        JointCntrlType
        JointMoveCmd

        galen_joint_positions
        
        prev_cmd
        Ts
    end

    methods
        % constructor
        function self = galen_interface()
            % stop and start the ross communication

            % setenv('ROS_MASTER_URI','http://10.0.0.192:11311')
            % setenv('ROS_IP','10.0.0.141') %Home
            % setenv('ROS_MASTER_URI','http://10.0.0.192:11311')
            % setenv('ROS_MASTER_URI','http://ubuntu:11311')
            setenv('ROS_IP','10.203.104.9') %Campus
            
            setenv('ROS_MASTER_URI','http://192.168.127.148:11311')
            rosshutdown;
            fprintf('Shutdown... \n')
            % rosinit('http://ubuntu:11311/'); % may need to change depending on how you choose to speak with ros

            rosinit('http://192.168.127.148:11311/');

            % rosinit('http://10.0.0.192:11311')
            fprintf('Initialized...')
            % for rosinit you need to include the name of the ros master
            % node or IP address. See rosinit documentation for more
            % details.
            fprintf('Creating Subscribers... ')
            % subscribers
            self.galen_joint_states_sub = rossubscriber('/ambf/env/Base/State','ambf_msgs/RigidBodyState',"DataFormat","struct");
            self.E35_wrench_sub = rossubscriber('/ambf/env/Endoscope35degreeinREMS/State','ambf_msgs/RigidBodyState',"DataFormat","struct");
            fprintf('Creating Publishers... ')
            % publishers
            self.galen_joint_cmd_pub = rospublisher('/ambf/env/Base/Command','ambf_msgs/RigidBodyCmd',"DataFormat","struct");

            fprintf('Receiving Initial States... ')
            % Grab the current state of the robot
            self.galen_joint_states = receive(self.galen_joint_states_sub);
            fprintf('\n   Joint State received \n ')
            self.E35state = receive(self.E35_wrench_sub);
            fprintf('\n   Initial Force State received \n ')
            fprintf('Creating message.. ')
            % Initiate messages
            self.galen_command_message = rosmessage('ambf_msgs/RigidBodyCmd',"DataFormat","struct");
            fprintf('Complete\n')
            % Initiate property values and types assume velocity commands
            self.JointMoveCmd = int8([0,0,0,0,0,0]);
            self.JointCntrlType = single([2,2,2,2,2,2]);

            % For implimentation of admittance controller in the future
            % within this class we can assign the admittance properites
            self.m_ad = 5;
            self.b_ad = 100;

            self.galen_joint_positions = [0,0,0,0,0,0];
            self.Ts = 0;
            self.prev_cmd = single([0,0,0,0,0,0]);
        end

        % Set the control type for all joints
        % Inputs:
        %   control_type - a 1x6 vector of with the desired int
        %   representing control type or a char string
        function set_control_type(self,control_type)

            if isa(control_type,'char')
                switch control_type
                    case 'Effort'
                        type_message = [0,0,0,0,0,0];
                    case 'Position'
                        type_message = [1,1,1,1,1,1];
                    case 'Velocity'
                        type_message = [2,2,2,2,2,2];
                    otherwise
                        type_message = [2,2,2,2,2,2];
                end
            elseif isa(control_type,'double')
                type_message = control_type;
            end

            type_message = int8(type_message);
            self.galen_command_message.JointCmdsTypes = type_message;
            self.galen_command_message.JointCmds=single(self.JointMoveCmd);
            send(self.galen_joint_cmd_pub,self.galen_command_message);
            self.JointCntrlType  = type_message;

        end

        % grab the galen joint states from the state subscriber
        function joint_states = get_current_joints(self)

            self.galen_joint_states = receive(self.galen_joint_states_sub);
            switch self.JointCntrlType(1)
                case 0
                    joint_states = self.galen_joint_states.JointEfforts;
                case 1
                    joint_states = self.galen_joint_states.JointPositions;
                case 2
                    joint_states = self.galen_joint_states.JointVelocities;
            end

        end

        function joint_positions = extract_galen_pose(self)
            self.galen_joint_positions = self.galen_joint_states.JointPositions;
            joint_positions = self.galen_joint_positions;
        end

        % Move the galen robot given the command control type
        % Input:
        %   jointcmd - a 6x1 vector of desired states
        function move_galen(self,jointcmd)
            jointcmd = self.limiter(jointcmd);
            self.galen_command_message.JointCmds = jointcmd;
            send(self.galen_joint_cmd_pub,self.galen_command_message);
            self.prev_cmd = jointcmd;
        end


        function Fk = galen_fk(self,q_state)
            if nargin==2
                theta1 = q_state(1);
                theta2 = q_state(2);
                theta3 = q_state(3);
                theta4 = q_state(4);
                theta5 = q_state(5);
            else
                self.extract_galen_pose();
                theta1 = double(self.galen_joint_positions(1));
                theta2 = double(self.galen_joint_positions(2));
                theta3 = double(self.galen_joint_positions(3));
                theta4 = double(self.galen_joint_positions(5));
                theta5 = double(self.galen_joint_positions(6));
            end
   

            Fk = galen_fk2([theta1,theta2,theta3,theta4,theta5]);

        end

        function J = galen_jacob(self,q_state)
            if nargin==2
                theta1 = q_state(1);
                theta2 = q_state(2);
                theta3 = q_state(3);
                theta4 = q_state(4);
                theta5 = q_state(5);
            else
                self.extract_galen_pose();
                theta1 = double(self.galen_joint_positions(1));
                theta2 = double(self.galen_joint_positions(2));
                theta3 = double(self.galen_joint_positions(3));
                theta4 = double(self.galen_joint_positions(5));
                theta5 = double(self.galen_joint_positions(6));
            end

            J = galen_jacob2([theta1,theta2,theta3,theta4,theta5]);
            J(1:3,4:5) = zeros(3,2);
            J = round(J,4);
        end


        function Wrench = read_E35_wrench(self)
            Wrench = zeros(6,1);
            self.E35state = receive(self.E35_wrench_sub);
            Wrench(1) = self.E35state.Wrench.Force.X;
            Wrench(2) = self.E35state.Wrench.Force.Y;
            Wrench(3) = self.E35state.Wrench.Force.Z;
            Wrench(4) = self.E35state.Wrench.Torque.X;
            Wrench(5) = self.E35state.Wrench.Torque.Y;
            Wrench(6) = self.E35state.Wrench.Torque.Z;
        end


        function jointcmd = limiter(self,jointcmd)
            if self.Ts>5
                tic
                self.Ts = toc;
            else
                self.Ts = toc;
            end
            %limit acceleration
          
            for k=1:6
                if k<4
                    if (jointcmd(k)-self.prev_cmd(k))/self.Ts>200
                        jointcmd(k) = 200*self.Ts+self.prev_cmd(k);
                    elseif (jointcmd(k)-self.prev_cmd(k))/self.Ts<-200
                        jointcmd(k) = -200*self.Ts+self.prev_cmd(k);
                    end
                else
                    if (jointcmd(k)-self.prev_cmd(k))/self.Ts>45*pi/180
                        jointcmd(k) = (45*pi/180)*self.Ts+self.prev_cmd(k);
                    elseif (jointcmd(k)-self.prev_cmd(k))/self.Ts<-45*pi/180
                        jointcmd(k) = (-45*pi/180)*self.Ts+self.prev_cmd(k);
                    end
                end
            end
            
            %limit velocity
            if jointcmd(1)>0.04
                jointcmd(1) = 0.04;
            elseif jointcmd(1)<-.04
                jointcmd(1) = -0.04;
            end
            if jointcmd(2)>0.04
                jointcmd(2) = 0.04;
            elseif jointcmd(2)<-.04
                jointcmd(2) = -0.04;
            end
            if jointcmd(3)>0.04
                jointcmd(3) = 0.04;
            elseif jointcmd(3)<-.04
                jointcmd(3) = -0.04;
            end
            if jointcmd(4)>45*pi/180
                jointcmd(4) = 45*pi/180;
            elseif jointcmd(4)<-45*pi/180
                jointcmd(4) = -45*pi/180;
            end
            if jointcmd(5)>45*pi/180
                jointcmd(5) = 45*pi/180;
            elseif jointcmd(5)<-45*pi/180
                jointcmd(5) = -45*pi/180;
            end
            if jointcmd(6)>45*pi/180
                jointcmd(6) = 45*pi/180;
            elseif jointcmd(6)<-45*pi/180
                jointcmd(6) = -45*pi/180;
            end

        end

    end % methods

end % class

