diff --git a/real_robot/coordination_formation_control_pkg/README.md b/real_robot/coordination_formation_control_pkg/README.md index 9f4aa13ef081db30e6eb3495228b18c58c90241c..ec46318bded23e6d7957ccc63703415cc9cb0adf 100644 --- a/real_robot/coordination_formation_control_pkg/README.md +++ b/real_robot/coordination_formation_control_pkg/README.md @@ -16,3 +16,6 @@ implement modules - [X] send command should include conversion to velocity . [ ] test omniwheels node - [ ] Implement swam controller + +- Dependencies https://github.com/ethz-asl/libnabo.git +It uses this ibrary to implement a k nearest negibour search diff --git a/real_robot/coordination_formation_control_pkg/include/shared_definitions.h b/real_robot/coordination_formation_control_pkg/include/shared_definitions.h index ab0bf2dc3c8e89c636052a0c0ba6e702e23bbfa0..be39e62b6d15e086aaf549c22e3b60d18312e6aa 100644 --- a/real_robot/coordination_formation_control_pkg/include/shared_definitions.h +++ b/real_robot/coordination_formation_control_pkg/include/shared_definitions.h @@ -15,6 +15,15 @@ typedef enum robot_type } robot_type_t; +/* formation types*/ +typedef enum formation_matrix +{ + PAIR =2, + TRIANGLE = 3, + SQUARE = 4, + PENTAGON = 5 +}formation_matrix_t; + /* robot state-ugv and agv*/ typedef struct robot_state { @@ -27,6 +36,7 @@ typedef struct robot_state typedef struct robot_ind { int id; /*d*/ + int id_m; /* position in the distance vector*/ std::string robot_name; /* robot name*/ robot_type_t t; /*robot_type*/ float safe_ditance; /* important to prevent mistakenly putting a very small number*/ @@ -76,7 +86,7 @@ typedef struct form_gains typedef struct form_param { float d; /* distance between agent*/ - float formation_t; /* type must be minimum 2, 3: tuangle formation, 4 suare, 5 pentagon*/ + int formation_t; /* type must be minimum 2, 3: tuangle formation, 4 suare, 5 pentagon*/ float k; float ratio; /* ratio between dist inter and obstacle*/ float eps; @@ -85,10 +95,13 @@ typedef struct form_param float h_alpha; float h_beta; float d_obs; - float nav_type; /* convergence approach -1, parallel approach 2*/ - float integrator; /* single/ double integrator*/ + int nav_type; /* convergence approach -1, parallel approach 2*/ + int integrator; /* single/ double integrator*/ float dt; /* sample time*/ float int_max; /* maximum integral*/ + float r; /* maximum allowd Neighbourhood radios, this is set based on the k ratio*/ + float r_obs; /* maximum allowd Neighbourhood radios, this is set based on the k ratio*/ + }form_param_t; /* formation configuraiton*/ @@ -100,7 +113,7 @@ typedef struct formation_config -typedef Eigen::Matrix<double, 2,6> input_vector_t; +typedef Eigen::Matrix<float, 2,6> input_vector_t; /* input vector conatains the following * totall input x y * ofr debugging I print the remaining input @@ -110,7 +123,10 @@ typedef Eigen::Matrix<double, 2,6> input_vector_t; * input orientation */ - typedef Eigen::Matrix <double, 2, 1>row_vector_2d_t; + typedef Eigen::Matrix <float, 2, 1>row_vector_2d_t; + + typedef Eigen::Matrix <float,3,1> row_vector_3d_t; + diff --git a/real_robot/coordination_formation_control_pkg/include/swamControllerAlg.h b/real_robot/coordination_formation_control_pkg/include/swamControllerAlg.h index c51b84511575b71650f25188030f55eb3ec0f81f..10656877b4c1ecbbecfcc06ac6d12064231838a2 100644 --- a/real_robot/coordination_formation_control_pkg/include/swamControllerAlg.h +++ b/real_robot/coordination_formation_control_pkg/include/swamControllerAlg.h @@ -1,15 +1,23 @@ #ifndef SWAM_CONTROLLER_ALG_UAV_H #define SWAM_CONTROLLER_ALG_UAV_H +#include "ros/ros.h" +#include <eigen3/Eigen/Dense> +#include "math.h" #include "shared_definitions.h" +#include <vector> class swamControllerAlg { private: formation_config_t form_param; /*parameters of the formation*/ row_vector_2d_t integral_error; - row_vector_2d_t previous_error; + row_vector_2d_t previous_integral_error; robot_ind_t robot_id; + formation_matrix_t form_type; + Eigen::MatrixXf iad; /* iter agent distances*/ + int N; /* number of agents*/ + public: /* constructor*/ @@ -21,10 +29,28 @@ public: protected: /* configuration formation information like interagent distances*/ - void configure_formation(); - - - + void configureFormation(); + std::vector<std::pair<int, float>> sortNeigbour(Eigen::MatrixXf M, row_vector_2d_t q); + /* compute fi function*/ + float computeFi(row_vector_2d_t qj, row_vector_2d_t qi, float a, float b, float h, float eps, float r, float d); + /* compute sigma norm*/ + float computeSigmaNorm(float z, float eps); + /*compute ph bumber function*/ + float ph(float z, float h); + /* compute fi*/ + float computefi(float a, float b, float z); + /* compute sigmaone*/ + float sigmaOne(float z); +/* sigma norm vector form*/ + row_vector_2d_t sigmaOne(row_vector_2d_t z); + /* compute nij*/ + row_vector_2d_t computeNij(row_vector_2d_t qj, row_vector_2d_t qi, float eps); + /*compute aij*/ + float computeAij(row_vector_2d_t qj, row_vector_2d_t qi, float r,float eps, float h); + /* compute fi function for the obstacle*/ + float computeFiB(row_vector_2d_t q_hat_i, row_vector_2d_t qi, float h_beta, float d_obs, float eps); + /* compute Bij*/ + float computeBij(row_vector_2d_t q_hat_i, row_vector_2d_t qi, float d_obs, float eps, float h_beta); }; diff --git a/real_robot/coordination_formation_control_pkg/src/drone_node.cpp b/real_robot/coordination_formation_control_pkg/src/drone_node.cpp index e6b5f1c24c5ee389f3f8841c87a0a9ba2553b5ee..4adc2076d302d644d30d4e60505276d6d3d0fb87 100644 --- a/real_robot/coordination_formation_control_pkg/src/drone_node.cpp +++ b/real_robot/coordination_formation_control_pkg/src/drone_node.cpp @@ -79,7 +79,7 @@ int main(int argc, char **argv) { } else { - robot_ind.leader = false; + robot_ind.leader = true; } /* initialization of the formation configuration */ @@ -145,6 +145,7 @@ int main(int argc, char **argv) { uav_def.robot_name = "cf"; uav_def.t = CRAZYFLIE; uav_def.id = i; + uav_def.id_m = i-1; sucess_initialization = false; auto * uav = new robotState(uav_def, nh, sucess_initialization); uavs.push_back(uav); diff --git a/real_robot/coordination_formation_control_pkg/src/omniwheel_node.cpp b/real_robot/coordination_formation_control_pkg/src/omniwheel_node.cpp index d3ee72a128bbad530bf3e15607c887afb884b59c..5f5d1f4ca59ce274617745cd56b94109d832eb04 100644 --- a/real_robot/coordination_formation_control_pkg/src/omniwheel_node.cpp +++ b/real_robot/coordination_formation_control_pkg/src/omniwheel_node.cpp @@ -112,6 +112,7 @@ int main(int argc, char **argv) { ugv_def.robot_name = "omniwheel"; ugv_def.t = OMNIWHEELS; ugv_def.id = i; + ugv_def.id_m = 1; sucess_initialization = false; auto * ugv = new robotState(ugv_def, nh, sucess_initialization); ugvs.push_back(ugv); diff --git a/real_robot/coordination_formation_control_pkg/src/swamControllerAlg.cpp b/real_robot/coordination_formation_control_pkg/src/swamControllerAlg.cpp index 3c82f9d4e28e9e079d8fe191b79c4cd04a001751..8f839e25181f996bb7cd71962964d08c8d06d618 100644 --- a/real_robot/coordination_formation_control_pkg/src/swamControllerAlg.cpp +++ b/real_robot/coordination_formation_control_pkg/src/swamControllerAlg.cpp @@ -5,6 +5,59 @@ swamControllerAlg::swamControllerAlg(robot_ind_t indentification, formation_config_t formation_config) { + this->form_param = formation_config; + this->robot_id = indentification; + this->integral_error << 0,0,0; + this->previous_integral_error << 0,0,0; + + /* we want to ensure the following condition is met + * c1_alpha < c1_gamma < c1_beta*/ + if((this->form_param.gain.c1_alpha > this->form_param.gain.c1_gamma) || (this->form_param.gain.c1_gamma > this->form_param.gain.c1_beta)) + { + ROS_INFO("Swam controller: Following condition not met!!!!!! c1_alpha < c1_gamma < c1_beta"); + EXIT_FAILURE; + } + /* make sure that the following codition is met + * 0 < a <= b*/ + if((0 > this->form_param.param.a) || (this->form_param.param.a > this->form_param.param.b)) + { + ROS_INFO("Swam controller: Following condition not met!!!!!! 0 < a <= b"); + EXIT_FAILURE; + } + if((this->form_param.param.nav_type != 1) || (this->form_param.param.nav_type != 2)) + { + ROS_INFO("Swam Controller: Navigational_type should be 1-converge approach or 2 - parallel approach"); + EXIT_FAILURE; + } + + /* compute the c2 for each gains*/ + this->form_param.gain.c2_alpha = 2*sqrt(this->form_param.gain.c1_alpha); + this->form_param.gain.c2_gamma = 2*sqrt(this->form_param.gain.c1_gamma); + this->form_param.gain.c2_beta = 2*sqrt(this->form_param.gain.c1_beta); + + /* formation Neighbourhood distance*/ + this->form_param.param.r = this->form_param.param.k*this->form_param.param.d; + /* set the obstcale avoidance parameters*/ + this->form_param.param.d_obs = this->form_param.param.d*this->form_param.param.ratio; + this->form_param.param.r_obs = this->form_param.param.k*this->form_param.param.d_obs; + /* ensure there is no collision between robots and obstacles*/ + float total_safety_distance = this->robot_id.robot_size/2 + this->robot_id.safe_ditance; + if((total_safety_distance < this->form_param.param.d) || (total_safety_distance < this->form_param.param.d_obs)) + { + ROS_INFO("Swam controller: The distance between the robot and other robot or obstacle might result in collision: total_safety_distance: %f robot_size: %f safe_ditance: %f", + total_safety_distance, this->form_param.param.d, this->robot_id.safe_ditance); + EXIT_FAILURE; + } + + this->N = this->form_param.param.formation_t; + this->form_type = formation_matrix_t(this->form_param.param.formation_t); + + /* formation configuration steps*/ + this->configureFormation(); + + + + ROS_INFO("Robot %s_%d swam controller initialized", this->robot_id.robot_name.c_str(), this->robot_id.id); } @@ -14,9 +67,369 @@ swamControllerAlg::~swamControllerAlg() } +void swamControllerAlg::configureFormation() +{ + /* this consists of predefined formation*/ + float d = this->form_param.param.d; + float h = 0; + + if(this->form_type == PAIR) + { + this->iad(2,2); + this->iad << 0, d, + d, 0; + } + else if(this->form_type == TRIANGLE) + { + this->iad(3,3); + + this->iad << 0, d, d, + d, 0, d, + d, d, 0; + + } + else if(this->form_type == SQUARE) + { + h = sqrt(2*d*d); // diagonal distance + this->iad(4,4); + this->iad << 0, d, h, d, + d,0,d,h, + h,d,0,d, + d,h,d,0; + } + else if(this->form_type == PENTAGON) + { + h = d + 2*d*cos(72*M_PI/180.0); // diagonal distance + this->iad(4,4); + this->iad << 0, d, h, h, d, + d,0,d,h,h, + h,d,0,d,h, + h,h,d,0,d, + d,h,h,d,0; + } + else + { + ROS_INFO("Swam Controller: Wrong formation type: 2 PAIR, 3 TRIANGLE, 4 SQUARE, 5 PENTAGON "); + EXIT_FAILURE; + } + ROS_INFO("Swam Controller: interagent matrix"); + std::cout << this->iad << std::endl; + + +} + /* main controller implementation*/ input_vector_t swamControllerAlg::controller(Eigen::MatrixXf q, Eigen::MatrixXf p, waypoint_t ref, obstacle_t* obs) { + /* implementation intended for control in the x y plane*/ + /* make sure that the number of agent match the matrix*/ + if((q.cols() != this->N ) || (p.cols() != this->N )) + { + ROS_INFO("Swam controller: p and q matrix is not correct, Number of agent: %d p/q column size: %d/%d", this->N, (int)q.cols(), (int)p.cols()); + EXIT_FAILURE; + } + /* formation computation*/ + row_vector_2d_t qi,pi,qj,pj; + qi(0) = q(0,this->robot_id.id_m); + qi(1) = q(1,this->robot_id.id_m); + pi(0) = p(0,this->robot_id.id_m); + pi(1) = p(1,this->robot_id.id_m); + std::vector<std::pair<int, float>> neighbour_sorted = this->sortNeigbour(q, qi); + row_vector_2d_t u_formation;//(0.0,0.0); + row_vector_2d_t grad_term;//(0.0,0.0); + row_vector_2d_t cons_term;//(0.0,0.0); + row_vector_2d_t deriv_term;//(0.0,0.0); + row_vector_2d_t error_grad_term;//(0.0,0.0); + u_formation << 0.0,0.0; + grad_term << 0.0,0.0; + cons_term << 0.0,0.0; + deriv_term << 0.0,0.0; + error_grad_term << 0.0,0.0; + + float fi,aij,d; + row_vector_2d_t nij; + + + for(int i = 0; i < this->N; i++) + { + /* compute input for those in the neighbour*/ + if(neighbour_sorted[i].second > this->form_param.param.r) + { + break; + } + /* only for neighbours*/ + if(i != this->robot_id.id_m) + { + qj(0) =q(0,i); + qj(1) =q(1,i); + pj(0) =p(0,i); + pj(1) =p(1,i); + d = this->iad(this->robot_id.id_m,i); /* distance between neighbours*/ + // compute the gradient term + fi = this->computeFi(qj,qi,this->form_param.param.a,this->form_param.param.b,this->form_param.param.h_alpha, + this->form_param.param.eps,this->form_param.param.r,d); + nij = this->computeNij(qj,qi,this->form_param.param.eps); + grad_term = grad_term + fi*nij; + // consesus term + if (this->form_param.param.integrator == 2) + { + aij = this->computeAij(qj,qi,this->form_param.param.r,this->form_param.param.eps,this->form_param.param.h_alpha); + cons_term = cons_term + aij*(pj-pi); + } + + } + } + error_grad_term = grad_term; + u_formation = grad_term*this->form_param.gain.c1_alpha+cons_term*this->form_param.gain.c2_alpha; + + + /* obstacle formation*/ + row_vector_2d_t u_obstacle; + u_obstacle << 0.0,0.0; //(0.0,0.0); + + if (obs != NULL) + { + /* if we have obstacle*/ + float obs_size = sizeof(obs)/sizeof(*obs); + row_vector_2d_t grad_term_obs, u_obstacle;//(0.0,0.0); + row_vector_2d_t cons_term_obs;//(0.0,0.0); + grad_term_obs << 0.0,0.0; + cons_term_obs << 0.0,0.0; + u_obstacle << 0.0,0.0; + row_vector_2d_t yk, ak, q_hat_i, p_hat_i; + float Rk, mu; + Eigen::MatrixXf a__; + for(int o = 0; o<obs_size; o++) + { + yk(0) = obs[o].q(0); + yk(1) = obs[o].q(1); + Rk = obs[o].radius; + mu = Rk/(qi-yk).norm(); + ak = (qi-yk)/(qi-yk).norm(); + a__ = ak*ak.transpose(); + Eigen::Matrix2f P; + P << 1.0, 0.0, 1.0,0.0; + q_hat_i = mu*qi+(1-mu)*yk; + p_hat_i = mu*(P-a__)*pi; + float d2obs = (qi-q_hat_i).norm(); + if (d2obs < this->form_param.param.r_obs) /* compute for neighbour obstacles*/ + { + /* gradient term*/ + float fi_beta = this->computeFiB(q_hat_i,qi,this->form_param.param.h_beta,this->form_param.param.d_obs,this->form_param.param.eps); + row_vector_2d_t nij = this->computeNij(q_hat_i, qi, this->form_param.param.eps); + grad_term_obs = grad_term_obs+fi_beta*nij; + /*consesus term*/ + if (this->form_param.param.integrator == 2) + { + float bij = this->computeBij(q_hat_i,qi,this->form_param.param.d_obs,this->form_param.param.eps, this->form_param.param.h_beta); + cons_term_obs = cons_term_obs + bij*(p_hat_i-pi); + } + } + } + + u_obstacle = this->form_param.gain.c1_beta*grad_term_obs + this->form_param.gain.c2_beta*cons_term_obs; + } + /* navigational term*/ + row_vector_2d_t u_navigation; + u_navigation << 0.0,0.0; + row_vector_2d_t q_ref,p_ref; + q_ref << ref.q(0), ref.q(1); + p_ref << ref.p(0), ref.p(1); + row_vector_2d_t centroid_q, centroid_p; + centroid_q << q.col(0).mean(), q.col(1).mean(); + centroid_p << p.col(0).mean(), p.col(1).mean(); + if (this->robot_id.leader == true) + { + + + if(this->form_param.param.nav_type == 1) + { + u_navigation = -this->form_param.gain.c1_gamma*(qi-q_ref) -this->form_param.gain.c2_gamma*(pi-p_ref); + } + else + { + + u_navigation = -this->form_param.gain.c1_gamma*(centroid_q-q_ref) -this->form_param.gain.c2_gamma*(centroid_p-p_ref); + + } + } + + // orienation term + row_vector_2d_t u_orientation; + u_orientation << 0.0,0.0; + if (ref.orientation != NOORIENTATION) + { + float angle = ref.orientation * M_PI /180; + // convert to 3d + row_vector_3d_t desired_vector(cos(angle), sin(angle),0); + row_vector_3d_t q_centroid, q_leader; + if (this->robot_id.leader == true) + { + q_centroid << centroid_q(0), centroid_q(1),0; + q_leader << q(0,0),q(1,0),0; + } + else + { + q_centroid << centroid_q(0), centroid_q(1),0; + q_leader << q(0,0),q(1,0),0; + } + float dlc = (q_leader-q_centroid).norm(); // distance from centroid to leader drone + row_vector_3d_t lcv = q_leader-q_centroid; // vector from centroid to leader + row_vector_3d_t dcv = dlc*desired_vector; // vector from centroid to desired final pose of leader + float acl = atan2(lcv(1), lcv(0)); // angle of vector from centroid to leadership dron + float acc = atan2(dcv(1), dcv(0)); // angle of vector from fron centroid to final drone pose + if(this->robot_id.leader == false) + { + // not necessaty TODO improve to get angle from 0 to 360 + acl = atan(lcv(1)/lcv(0)); // angle of vector from centroid to leadership dron + acc = atan(dcv(1)/dcv(0)); // + } + float alpha = acc-acl; // angle between vector lcv and dcv + // we want to compute the rotation acceleration + // direction + row_vector_3d_t q_current(qi(0), qi(1),0); // current drone pose + row_vector_3d_t cq_hat = q_current - q_centroid; // vector from cwentroid to current drone + row_vector_3d_t pot(0,0,-alpha); + row_vector_3d_t rot_d = cq_hat.cross(pot) ;// rotation direction + + if(rot_d.norm() == 0) + { + rot_d << 0,0,0; + } + else + { + rot_d = rot_d/rot_d.norm(); // convert to unit vector + } + + row_vector_2d_t final_rot_d(rot_d(0), rot_d(1)); + + u_orientation = this->form_param.gain.c1_theta*abs(alpha)*final_rot_d; + } + + /* integral term*/ + row_vector_2d_t u_integral; + u_integral << 0.0,0.0; + this->integral_error = this->integral_error + error_grad_term*this->form_param.param.dt; + u_integral = this->form_param.gain.c1_delta *this->integral_error; + + + row_vector_2d_t total_input = u_formation + u_navigation + u_integral + u_orientation+u_obstacle; + input_vector_t input2send; + input2send.col(0) = total_input; + input2send.col(1) = u_formation; + input2send.col(2) = u_obstacle; + input2send.col(3) = u_navigation; + input2send.col(4) = u_orientation; + input2send.col(5) = u_integral; + + return (input2send); + + + +} +// sort the neighbour distances +typedef std::function<bool(std::pair<int, float>, std::pair<int, float>)> Comparator; +Comparator compFunctor = + [](std::pair<int, float> elem1, std::pair<int, float> elem2) { + return elem1.second < elem2.second; + }; + + +std::vector<std::pair<int, float>> swamControllerAlg::sortNeigbour(Eigen::MatrixXf M, row_vector_2d_t q) +{ + std::vector<std::pair<int, float>> neighbour_dist; + float dist; + /* Compute the distance*/ + row_vector_2d_t qj; + for (int i = 0; i <this->N;i++) + { + qj(0) = M(0,i); + qj(1) = M(1,i); + dist = (q-qj).norm(); + neighbour_dist.emplace_back(std::pair<int,float>(i, dist)); + } + /* sort the distance*/ + std::sort(neighbour_dist.begin(), neighbour_dist.end(), compFunctor); + + return neighbour_dist; + +} + +float swamControllerAlg::computeFi(row_vector_2d_t qj, row_vector_2d_t qi, float a, float b, float h, float eps, float r, float d) +{ + /* qj neighbour + * qi drone itself*/ + float z_sn = this->computeSigmaNorm((qj-qi).norm(), eps); + float r_sn = this->computeSigmaNorm(r,eps); + float d_sn = this->computeSigmaNorm(d,eps); + return (this->ph(z_sn/r_sn,h) * this->computefi(a,b,(z_sn-d_sn))); +} + +float swamControllerAlg::computeSigmaNorm(float z, float eps) +{ + return((1.0/eps)*(sqrt(1.0+eps*z*z)-1.0)); +} + +float swamControllerAlg::ph(float z, float h) +{ + if((z>= 0) && (z <h)) + { + return 1; + } + else if((z>=h) && (z<=1)) + { + return(0.5*(1.0+cos(M_PI*(z-h)/(1-h)))); + } + else + { + return 0; + } +} + +float swamControllerAlg::computefi(float a, float b, float z) +{ + float c = abs(a-b)/sqrt(4*a*b); + return (0.5*(((a+b)*this->sigmaOne(z+c))+(a-b))); } + +float swamControllerAlg::sigmaOne(float z) +{ + return (z/sqrt(1.0+(z*z))); +} +row_vector_2d_t swamControllerAlg::sigmaOne(row_vector_2d_t z) +{ + float n = z.norm(); + return (z/sqrt(1.0+(n*n))); +} +row_vector_2d_t swamControllerAlg::computeNij(row_vector_2d_t qj, row_vector_2d_t qi, float eps) +{ + row_vector_2d_t z = qj-qi; + float n = z.norm(); + return(z/sqrt(1.0+(eps*n*n))); + +} + +float swamControllerAlg::computeAij(row_vector_2d_t qj, row_vector_2d_t qi, float r, float eps, float h) +{ + float z_sn = this->computeSigmaNorm((qj-qi).norm(),eps); + float r_sn = this->computeSigmaNorm(r,eps); + return (this->ph(z_sn/r_sn,h)); + +} + +float swamControllerAlg::computeFiB(row_vector_2d_t q_hat_i,row_vector_2d_t qi, float h_beta, float d_obs, float eps) +{ + float z_sn = this->computeSigmaNorm((q_hat_i-qi).norm(),eps); + float d_obs_sn = this->computeSigmaNorm(d_obs,eps); + return (this->ph(z_sn/d_obs_sn, h_beta)* (this->sigmaOne(z_sn-d_obs_sn)-1)); +} + + +float swamControllerAlg::computeBij(row_vector_2d_t q_hat_i, row_vector_2d_t qi, float d_obs, float eps, float h_beta) +{ + float z_sn = this->computeSigmaNorm((q_hat_i-qi).norm(),h_beta); + float d_obs_sn = this->computeSigmaNorm(d_obs,eps); + return (this->ph(z_sn/d_obs_sn,h_beta)); +} diff --git a/simulation/controller/SwamControllerAlg.asv b/simulation/controller/SwamControllerAlg.asv new file mode 100644 index 0000000000000000000000000000000000000000..167694d7606d46da9ef52d918de705fee8e252c8 --- /dev/null +++ b/simulation/controller/SwamControllerAlg.asv @@ -0,0 +1,431 @@ +classdef SwamControllerAlg + properties + formation_param % information on paramters + gain_param % struct gains c1 c2 for alpha(agent) c1 c2(obstacle) c1 c2(gamma) + indx %position of the robot in the distance matrix + integral_error % store the integrator + previous_error % store for integral control + + + end + + properties (Access = private) + robot_size %impose robot safety circle diameter + id % to indxentify the robot + end + + properties (Constant) + safe_distance = 0.15 % safety distance around the robot in + end + + + methods + % -- constructor + function thisSwamControllerAlg = SwamControllerAlg(robot_size, id,indx,form_param, gains) + % Important, this computation is valindx for 2d positions, 3d not + % included + thisSwamControllerAlg.robot_size = robot_size+thisSwamControllerAlg.safe_distance; + thisSwamControllerAlg.indx = indx; + thisSwamControllerAlg.id = id; + thisSwamControllerAlg.gain_param = gains; + thisSwamControllerAlg.integral_error = [0;0]; + thisSwamControllerAlg.previous_error = [0;0]; + + % recompute c2 gain to ensure the following conditions + %c1_apha < c1_gamma< c1_beta + if false %gains.c1_alpha > gains.c1_gamma || gains.c1_gamma > gains.c1_beta + error("Gain conditions not meet: c1_alpha < c1_gamma < c1_beta"); + end + % recompute c2 gain based on the following equation c2 = + % c1_gamma = s*sqrt(c) based on original paper + thisSwamControllerAlg.formation_param = form_param; + % ensure 0 < a < b is met + if (0 > form_param.a) && (form_param.a >form_param.b) + error('Error. 0 < a <= b not met'); + + end + % safety + if form_param.nav_type <= 0 || form_param.nav_type >2 + error("Navigational_type should be 1-converge approach or 2 - parallel approach"); + end + if form_param.nav_type ~= 2 + thisSwamControllerAlg.gain_param.c2_alpha = 2*sqrt(thisSwamControllerAlg.gain_param.c1_alpha); + thisSwamControllerAlg.gain_param.c2_beta = 2*sqrt(thisSwamControllerAlg.gain_param.c1_beta); + thisSwamControllerAlg.gain_param.c2_gamma = 2*sqrt(thisSwamControllerAlg.gain_param.c1_gamma); + end + % formation + thisSwamControllerAlg.formation_param.r = form_param.k*form_param.dist; % Neighbourhood distance + + + % obstacle avoindxance parameters + thisSwamControllerAlg.formation_param.d_obs = form_param.ratio*form_param.dist; + thisSwamControllerAlg.formation_param.r_obs = form_param.k*thisSwamControllerAlg.formation_param.d_obs; + if thisSwamControllerAlg.formation_param.d_obs < thisSwamControllerAlg.robot_size/2 + error("Minimum distance to obstacle is too low and can be dangerous, increase d/d_ob ratio"); + end + + + % perform some formation configuration; + thisSwamControllerAlg = thisSwamControllerAlg.configureFormation(); + disp("Robot: " + id + " Initialized"); + + + end + % -- Function responsible forcreating the formation parameters + function thisSwamControllerAlg = configureFormation(thisSwamControllerAlg) + % ensure distance is more thatn robot safe area + dist = thisSwamControllerAlg.formation_param.dist; + form_t = thisSwamControllerAlg.formation_param.type; + if dist < (thisSwamControllerAlg.robot_size/2) + error("Distance between robot will result in collision minimum radius distance: " + thisSwamControllerAlg.robot_size/2); + end + + % formation configuration + if form_t == 2 + % 2 man formation following + thisSwamControllerAlg.formation_param.iad = [0,dist; %interagent distance + dist,0]; + thisSwamControllerAlg.formation_param.N = 2; % number of agent + thisSwamControllerAlg.formation_param.node_names = {'d1','d2'}; + + + + elseif form_t == 3 % triangular formation + thisSwamControllerAlg.formation_param.iad = [0,dist, dist; %interagent distance + dist, 0, dist; + dist, dist, 0]; + thisSwamControllerAlg.formation_param.N = 3; % number of agent + thisSwamControllerAlg.formation_param.node_names = {'d1','d2','d3'}; + + elseif form_t == 4 % square formation + h = sqrt(2*dist*dist); % diagonal distance + thisSwamControllerAlg.formation_param.iad = [0,dist, h, dist; %interagent distance + dist, 0,dist, h; + h,dist,0,dist; + dist,h, dist,0]; + thisSwamControllerAlg.formation_param.N = 4; % number of agent + thisSwamControllerAlg.formation_param.node_names = {'d1','d2','d3','d4'}; + + + elseif form_t == 5 % pentagon formation + h = dist + 2*dist*cos(deg2rad(72)); %diagonal distance + thisSwamControllerAlg.formation_param.iad = [0,dist,h,h,dist; %interagent distance + dist, 0, dist,h,h; + h,dist,0,dist, h; + h,h,dist,0,dist; + dist,h,h,dist,0]; + thisSwamControllerAlg.formation_param.N = 5; % number of agent + thisSwamControllerAlg.formation_param.node_names = {'d1','d2','d3','d4','d5'}; + + else + error("Wrong formation type: triangle:1 square:2 pentagon: 3"); + end + disp("Interagent distance matrix: "); + disp(thisSwamControllerAlg.formation_param.iad); + end + + % -- display formation + function showFormation(thisSwamControllerAlg) + figure + if thisSwamControllerAlg.formation_param.type > 2 + pgon1 = nsindxedpoly(thisSwamControllerAlg.formation_param.N,'Center',[0 0],'SindxeLength',thisSwamControllerAlg.formation_param.dist); + plot(pgon1) + for i =1:thisSwamControllerAlg.formation_param.N + text(pgon1.Vertices(i,1),pgon1.Vertices(i,2),"d" + string(i)); + + end + elseif thisSwamControllerAlg.formation_param.type == 2 + plot([0; thisSwamControllerAlg.formation_param.dist], [0,0]) + text(0,0,"d1") + text(thisSwamControllerAlg.formation_param.dist,0,"d2") + + end +% axis equal + + end + + % -- main controller + function [input, input_vec,q_obs_vector] = controller(thisSwamControllerAlg,q,p,ref,ob, ori, cooperation) + % q is the current position of each of the drone- column vector + % p is the current velocitu of each of the drones + % ob is the current position of the obstacles [x,y,r] + % orientatoin in degs + if size(q,1) > 2 + error("Controller is intended for 2d plane, not 3d, send x y inputs"); + end + + % ----- formation computation + q_rf = q'; % convert to row format + [ind,dist] = knnsearch(q_rf,q_rf(thisSwamControllerAlg.indx,:),"K",thisSwamControllerAlg.formation_param.N); + I = find(dist <= thisSwamControllerAlg.formation_param.r); + if size(I,2) == 1 % only robot % dangerours because in real world the position can be different for the same robot, do not implement this in the real robot + warning("Robot does not seem to have any neighbour, make sure this was done intentionally"); % TODO implement better logging + end + N_agent = ind(1,1:size(I,2));% agent neigbours + s_Na = size(N_agent,2); + u_formation = [0;0]; + grad_term = [0;0]; + cons_term = [0;0]; + deriv_term = [0;0]; + a = thisSwamControllerAlg.formation_param.a; + b = thisSwamControllerAlg.formation_param.b; + h_alpha = thisSwamControllerAlg.formation_param.h_alpha; + eps = thisSwamControllerAlg.formation_param.eps; + r = thisSwamControllerAlg.formation_param.r; + qi = q(:,thisSwamControllerAlg.indx); + pi_ = p(:,thisSwamControllerAlg.indx); + + + + for i = 1: s_Na + if N_agent(i) ~= thisSwamControllerAlg.indx % only for neigbours + qj = q(:,N_agent(i)); + pj = p(:,N_agent(i)); + if cooperation == 1 + d = thisSwamControllerAlg.formation_param.iad(thisSwamControllerAlg.indx,N_agent(i)); + else + d = thisSwamControllerAlg.formation_param.follow_dist; + end + % gradient term + fi = thisSwamControllerAlg.computeFi(qj,qi,a,b,h_alpha,eps,r,d); + nij = SwamControllerAlg.computeNij(qj,qi, eps); + grad_term = grad_term + fi*nij; + % consesus term + if thisSwamControllerAlg.formation_param.integrator == 2 + aij = SwamControllerAlg.computeAij(qj,qi,r,eps,h_alpha); + cons_term = cons_term +aij*(pj-pi_); + + deriv_term = deriv_term + aij*(qj-qi); + + end + + + end + + end + error_grad_term = grad_term; + u_formation = grad_term*thisSwamControllerAlg.gain_param.c1_alpha + cons_term*thisSwamControllerAlg.gain_param.c2_alpha; + % obstacle formation + % TODO current implementation only consindxers circular + % Todo plot gradient and potencial + % obstacles, include hyperplane + + if isempty(ob) + u_obstacle = [0;0]; +q_obs_vector = [0;0]; + + else + % compute position and velocity of obstacles + % projection of agent onto obstacle + ob_s = size(ob,2); + r_obs = thisSwamControllerAlg.formation_param.r_obs; + d_obs = thisSwamControllerAlg.formation_param.d_obs; + h_beta = thisSwamControllerAlg.formation_param.h_beta; + + % q_hat = zeros(2,ob_s); % positions of all the obstables + % p_hat = zeros(2,ob_s); % velocities of all the obstacles + % N_beta = []; + q_obs_vector = zeros(2,ob_s); + grad_term_obs = [0;0]; + cons_term_obs = [0;0]; + nav_term_obs = [0;0]; + for o = 1:ob_s + yk = ob(1:2,o); % center of obstacle + Rk = ob(3,o); % radius of obstacle + mu = Rk/norm(qi-yk); + ak = (qi-yk)/norm(qi-yk); + a__ = ak*ak'; + sa = size(a__,1); + P = eye(sa)- a__; + q_hat_i = mu*qi + (1-mu)*yk; + p_hat_i = mu *P*pi_; + +% qi = thisSwamControllerAlg.indx; +% if thisSwamControllerAlg.indx == 1 +% disp("Here"); +% end + d2obs = norm(qi- q_hat_i); + q_obs_vector(:,o) = q_hat_i; + if d2obs < r_obs %Neighbourhood obstacles of agent + % gradient term + fi_beta = SwamControllerAlg.computeFiB(q_hat_i,qi,h_beta,d_obs,eps); + nij = SwamControllerAlg.computeNij(q_hat_i,qi, eps); + grad_term_obs = grad_term_obs + fi_beta*nij; + % consesus term + if thisSwamControllerAlg.formation_param.integrator == 2 + + bij = SwamControllerAlg.computeBij(q_hat_i,qi,d_obs,eps, h_beta); + cons_term_obs = cons_term_obs +bij*(p_hat_i-pi_); + end +% +% nav_term_obs = nav_term_obs -thisSwamControllerAlg.gain_param.c1_gamma*(SwamControllerAlg.sigmaOne(q_hat_i-ref.q))- ... +% thisSwamControllerAlg.gain_param.c2_gamma*(p_hat_i-ref.p); + end + end +% + u_obstacle = thisSwamControllerAlg.gain_param.c1_beta* grad_term_obs ... + +thisSwamControllerAlg.gain_param.c2_beta*cons_term_obs; + + end + + % navigational term + % TODO: implement wait until formation potential has reach a + % threshold + u_navigation = [0;0]; + if cooperation == 1 + if thisSwamControllerAlg.formation_param.nav_type == 1 + % convergence approach + u_navigation = -thisSwamControllerAlg.gain_param.c1_gamma*(SwamControllerAlg.sigmaOne(qi-ref.q))- ... + thisSwamControllerAlg.gain_param.c2_gamma*(pi_-ref.p); + else + %parallel approach + % compute the centroindx info + mean_q = [mean(q(1,:)); mean(q(2,:))]; + mean_p = [mean(p(1,:)); mean(p(2,:))]; + + u_navigation = -thisSwamControllerAlg.gain_param.c1_gamma*(SwamControllerAlg.sigmaOne((mean_q-ref.q)))- ... + thisSwamControllerAlg.gain_param.c2_gamma*(mean_p-ref.p); +% u_navigation = sat_func(u_navigation,0.05); + + + end + end + + + % orientation term + % compute the centroindx vector + u_orientation = [0;0]; + if ~isempty(ori) + desired_angle = deg2rad(ori); + % convert to 3d + desired_vector = [cos(desired_angle); sin(desired_angle);0]; + if cooperation == 1 + q_centroid = [mean(q,2);0]; + q_leader = [q(:,1);0]; + else + q_centroid = [q(:,1);0]; + q_leader = [q(:,2);0]; + + end + dlc = norm(q_leader-q_centroid); % distance from centroid to leader drone + lcv = q_leader-q_centroid; % vector from centroid to leader + dcv = dlc*desired_vector; % vector from centroid to desired final pose of leader + acl =atan2(lcv(2), lcv(1));%angle of vector from centroid to leadership dron + acc = atan2(dcv(2), dcv(1)); % angle of vector from fron centroid to final drone pose + if cooperation == 2 + acl =atan(lcv(2)/ lcv(1));%angle of vector from centroid to leadership dron + acc = atan(dcv(2)/dcv(1)); % angle of vector from fron centroid to final drone pose + end + alpha = acc-acl;%angle between vector lcv and dcv + % ---- We want to compute the rotational acceleration + % direction + q_hat = [q(:,thisSwamControllerAlg.indx); 0];% current drone pose + cq_hat = q_hat - q_centroid;% vector from centroid to current drone + rot_d =cross(cq_hat, [0,0,-alpha]) ; %rotaiton direction + + if norm(rot_d) == 0 + rot_d = [0;0]; + else + rot_d = rot_d/norm(rot_d); % convert to unit vecor; + end + u_orientation = thisSwamControllerAlg.gain_param.c1_theta*abs(alpha)*[rot_d(1);rot_d(2)]; + end + + % integral controld + % error obtained from the formation gradient term + thisSwamControllerAlg.integral_error = thisSwamControllerAlg.integral_error + error_grad_term*thisSwamControllerAlg.formation_param.dt; + % saturate error to prevent anti-winup +% thisSwamControllerAlg.integral_error = sat_func( thisSwamControllerAlg.integral_error, thisSwamControllerAlg.formation_param.int_max); + u_integral = thisSwamControllerAlg.gain_param.c1_delta * thisSwamControllerAlg.integral_error; % + deriv_term*thisSwamControllerAlg.gain_param.c2_delta; +% u_derivative = ((error_grad_term-previous_error)/ thisSwamControllerAlg.formation_param.dt)*thisSwamControllerAlg.gain_param.c3_delta; +% thisSwamControllerAlg.previous_error = error_grad_term; +% ce = error_grad_term; + + input = u_formation + u_obstacle + u_navigation+u_orientation + u_integral+ 0; + input_vec = [u_formation,u_obstacle,u_navigation,u_orientation,u_integral]; % debug purposes +% potential = [potential_formation,potential_obstacle, potential_navigation, potential_orietation]; + end + + end + methods (Static) + + % - computes the sigma norm of a vector + function out = computeSigmaNorm(z,eps) + n = norm(z); + out = (1/eps)*((sqrt(1+eps*n*n))-1); + end + % compute the fi alpha function + function out = computeFi(qj,qi,a,b,h,eps,r,d) + % qj is the neighbour + % qi is the drone itself + % sigma norms of the error-cut off radius- desired distance + z_sn = SwamControllerAlg.computeSigmaNorm((qj-qi), eps); % this can be done outsindxe the function + r_sn = SwamControllerAlg.computeSigmaNorm(r, eps); + d_sn = SwamControllerAlg.computeSigmaNorm(d, eps); + out = SwamControllerAlg.ph(z_sn/r_sn,h) * SwamControllerAlg.fi(a,b,(z_sn-d_sn)); + end + + + % - computes the fi function + function out = fi( a,b,z) + c = abs(a-b)/sqrt(4*a*b); + out = 0.5 * (((a+b)*SwamControllerAlg.sigmaOne(z+c)) + (a-b)); + end + + % - computes the ph bumper function + function out = ph(z, h) + % the bumper function maps R+ to [0,1] + if max(size(z)) > 1 + error("Bumper function: value must be scalar") + end + if( z >= 0) && ( z<h) + out = 1; + elseif (z >= h) && (z<=1) + out = 0.5*(1+cos(pi*(z-h)/(1-h))); + else + out = 0; + end + + end + + % computes the sigma1 + function out = sigmaOne(z) + n = norm(z); + out = z/sqrt(1+(n*n)); + end + + % --- compute nij function neighbourhood function + function out = computeNij(qj,qi, eps) + % qj is the neighbour + % qi is the drone itself + z = qj -qi; + n = norm(z); + out = z/sqrt(1+(eps*n*n)); + + + end + + % -- compute the adjency matrix element + function out = computeAij(qj,qi,r,eps,h) + z_sn = SwamControllerAlg.computeSigmaNorm(qj-qi, eps); + r_sn = SwamControllerAlg.computeSigmaNorm(r,eps); + out = SwamControllerAlg.ph(z_sn/r_sn,h); + end + + % -- helper function for obstacle + % damping function + function out = computeFiB(qj,qi,h_beta,d_obs,eps) + z_sn = SwamControllerAlg.computeSigmaNorm(qj-qi,eps); + d_obs_sn = SwamControllerAlg.computeSigmaNorm(d_obs,eps); + out = SwamControllerAlg.ph(z_sn/d_obs_sn,h_beta) * (SwamControllerAlg.sigmaOne(z_sn-d_obs_sn)-1); + end + function out = computeBij(qj,qi,d_obs,eps, h) + z_sn = SwamControllerAlg.computeSigmaNorm(qj-qi,eps); + d_obs_sn = SwamControllerAlg.computeSigmaNorm(d_obs,eps); + out = SwamControllerAlg.ph(z_sn/d_obs_sn,h); + end + end + +end + +