int main(int argc, char **argv) {
    ros::init(argc, argv, "joint2_controller");
    ros::NodeHandle nh;
    ros::Duration half_sec(0.5);
    
    // make sure service is available before attempting to proceed, else node will crash
    bool service_ready = false;
    while (!service_ready) {
      service_ready = ros::service::exists("/gazebo/apply_joint_effort",true);
      ROS_INFO("waiting for apply_joint_effort service");
      half_sec.sleep();
    }
    ROS_INFO("apply_joint_effort service exists");

    ros::ServiceClient set_trq_client = 
       nh.serviceClient<gazebo_msgs::ApplyJointEffort>("/gazebo/apply_joint_effort");
    
    service_ready = false;
    while (!service_ready) {
      service_ready = ros::service::exists("/gazebo/get_joint_properties",true);
      ROS_INFO("waiting for /gazebo/get_joint_properties service");
      half_sec.sleep();
    }
    ROS_INFO("/gazebo/get_joint_properties service exists");
    
    ros::ServiceClient get_jnt_state_client = 
       nh.serviceClient<gazebo_msgs::GetJointProperties>("/gazebo/get_joint_properties");

    gazebo_msgs::ApplyJointEffort effort_cmd_srv_msg;
    gazebo_msgs::GetJointProperties get_joint_state_srv_msg;
    
    ros::Publisher trq_publisher = nh.advertise<std_msgs::Float64>("jnt2_trq", 1); 
    ros::Publisher vel_publisher = nh.advertise<std_msgs::Float64>("jnt2_vel", 1);     
    ros::Publisher pos_publisher = nh.advertise<std_msgs::Float64>("jnt2_pos", 1);  
    ros::Publisher joint_state_publisher = nh.advertise<sensor_msgs::JointState>("joint2_states", 1); 

    ros::Subscriber pos_cmd_subscriber = nh.subscribe("jnt2_pos_cmd",1,posCmdCB); 
     
    std_msgs::Float64 trq_msg;
    std_msgs::Float64 q1_msg,q1dot_msg;
    sensor_msgs::JointState joint_state_msg;

    double q1, q1dot;
    double dt = 0.01;
    ros::Duration duration(dt);
    ros::Rate rate_timer(1/dt);
    
    effort_cmd_srv_msg.request.joint_name = "joint2";
    effort_cmd_srv_msg.request.effort = 0.0;
    effort_cmd_srv_msg.request.duration= duration;

    get_joint_state_srv_msg.request.joint_name = "joint2";
    //double q1_des = 1.0;
    double q1_err;
    double Kp = 50.0;
    double Kv = 3;
    double trq_cmd;

    // set up the joint_state_msg fields to define a single joint,
    // called joint1, and initial position and vel values of 0
	joint_state_msg.header.stamp = ros::Time::now();
	joint_state_msg.name.push_back("joint2");
        joint_state_msg.position.push_back(0.0);
        joint_state_msg.velocity.push_back(0.0);
    while(ros::ok()) {    
        get_jnt_state_client.call(get_joint_state_srv_msg);
        q1 = get_joint_state_srv_msg.response.position[0];
        q1_msg.data = q1;
        pos_publisher.publish(q1_msg);
        
        q1dot = get_joint_state_srv_msg.response.rate[0];
        q1dot_msg.data = q1dot;
        vel_publisher.publish(q1dot_msg);

	joint_state_msg.header.stamp = ros::Time::now();
        joint_state_msg.position[0] = q1; 
        joint_state_msg.velocity[0] = q1dot;

	joint_state_publisher.publish(joint_state_msg);
        
        //ROS_INFO("q1 = %f;  q1dot = %f",q1,q1dot);
        //watch for periodicity
        q1_err= g_pos_cmd-q1;
        if (q1_err>M_PI) {
            q1_err -= 2*M_PI;
        }
        if (q1_err< -M_PI) {
            q1_err += 2*M_PI;
        }        
            
        trq_cmd = Kp*(q1_err)-Kv*q1dot;
        //trq_cmd = sat(trq_cmd, 10.0); //saturate at 1 N-m
        trq_msg.data = trq_cmd;
        trq_publisher.publish(trq_msg);
        // send torque command to Gazebo
        effort_cmd_srv_msg.request.effort = trq_cmd;
        set_trq_client.call(effort_cmd_srv_msg);
        //make sure service call was successful
        bool result = effort_cmd_srv_msg.response.success;
        if (!result)
            ROS_WARN("service call to apply_joint_effort failed!");
        ros::spinOnce();
	rate_timer.sleep();
  }
}
int main(int argc, char **argv) {
    ros::init(argc, argv, "two_wheel_pos_minimal_controller");
    ros::NodeHandle nh;
    ros::Duration duration(dt);  // duration for the torque to be exerted in the controller

    // get initialization message of robot swarm from parameter server
    std::string robot_model_name;
    int robot_quantity;
    bool get_name, get_quantity;
    get_name = nh.getParam("/robot_model_name", robot_model_name);
    get_quantity = nh.getParam("/robot_quantity", robot_quantity);
    if (!(get_name && get_quantity))
        return 0;  // return if fail to get parameter

    // resize global variables
    g_left_wheel_poses.resize(robot_quantity);
    g_left_wheel_vels.resize(robot_quantity);
    g_left_wheel_poses_cmd.resize(robot_quantity);
    g_right_wheel_poses.resize(robot_quantity);
    g_right_wheel_vels.resize(robot_quantity);
    g_right_wheel_poses_cmd.resize(robot_quantity);

    // initialize a subscriber for "two_wheel_poses"
    ros::Subscriber two_wheel_poses_subscriber = nh.subscribe("two_wheel_poses", 1, twoWheelPosesCallback);
    // initialize a subscriber for "two_wheel_poses_cmd"
    ros::Subscriber two_wheel_poses_cmd_subscriber =
        nh.subscribe("two_wheel_poses_cmd", 1, twoWheelPosesCmdCallback);

    while (!g_poses_callback_started) {
        ros::Duration(0.5).sleep();
        ros::spinOnce();
    }
    ROS_INFO("topic message from two_wheel_poses is ready");

    // initialize a service client to apply joint effort
    ros::ServiceClient set_wheel_torque_client = nh.serviceClient<gazebo_msgs::ApplyJointEffort>(
        "/gazebo/apply_joint_effort");
    gazebo_msgs::ApplyJointEffort set_wheel_torque_srv_msg;  // service message
    set_wheel_torque_srv_msg.request.effort = 0.0;
    set_wheel_torque_srv_msg.request.duration = duration;

    // make sure apply_joint_effort service is ready
    ros::Duration half_sec(0.5);
    bool service_ready = false;
    while (!service_ready) {
        service_ready = ros::service::exists("/gazebo/apply_joint_effort",true);
        ROS_INFO("waiting for apply_joint_effort service");
        half_sec.sleep();
    }
    ROS_INFO("apply_joint_effort service is ready");

    // initialize a service to change g_kp & g_kv, service name is "two_wheel_kpkv"
    ros::ServiceServer kpkv_service = nh.advertiseService("two_wheel_kpkv", twoWheelKpkvCallback);

    // prepare control variables
    ros::Rate rate_timer(1/dt);
    double left_wheel_pos_err;
    double left_wheel_torque;
    double right_wheel_pos_err;
    double right_wheel_torque;
    std::string left_motor_name;  // name of left motor
    std::string right_motor_name;  // name of right motor
    // control loop
    while (ros::ok()) {
        // ROS_INFO("in the loop...");  // for debug
        // apply joint effort loop
        for (int i=0; i<robot_quantity; i++) {
            // calculate the error
            left_wheel_pos_err = g_left_wheel_poses_cmd[i] - g_left_wheel_poses[i];
            right_wheel_pos_err = g_right_wheel_poses_cmd[i] - g_right_wheel_poses[i];

            // do not watch for periodicity, that is, making error in (-M_PI, M_PI)
            // because wheels are continuous rotating, angle could not always be in that range

            // calculate the torque
            left_wheel_torque = g_kp * left_wheel_pos_err - g_kv * g_left_wheel_vels[i];
            right_wheel_torque = g_kp * right_wheel_pos_err - g_kv * g_right_wheel_vels[i];
            // left_wheel_torque = 0.00001;  // for debug

            std::string s_index = intToString(i);
            // actually cheat here, no better way to get the key words "left_motor" and "right_motor"
            left_motor_name = robot_model_name + "_" + s_index + "::left_motor";
            right_motor_name = robot_model_name + "_" + s_index + "::right_motor";

            // set left wheel torque
            set_wheel_torque_srv_msg.request.joint_name = left_motor_name;
            set_wheel_torque_srv_msg.request.effort = left_wheel_torque;
            // ROS_INFO_STREAM("left_wheel_torque " << left_wheel_torque);  // for debug
            set_wheel_torque_client.call(set_wheel_torque_srv_msg);

            // set right wheel torque
            set_wheel_torque_srv_msg.request.joint_name = right_motor_name;
            set_wheel_torque_srv_msg.request.effort = right_wheel_torque;
            // ROS_INFO_STREAM("right_wheel_torque " << right_wheel_torque);  // for debug
            set_wheel_torque_client.call(set_wheel_torque_srv_msg);
        }

        // frequency control
        ros::spinOnce();  // update pos_cmd
        rate_timer.sleep();
    }
}