6.6.6. **realtime_control_sample.cpp** ------------------------------------------------- This example (``6_realtime_control_sample.cpp``) demonstrates the minimal procedure required to execute **real-time (RT) control** of the robot using. - 1 ms high-frequency servo commands (``servoj_rt``), - quintic trajectory generation (5th order polynomial), - real-time thread scheduling (Linux/Pthread or Xenomai), - and safety-aware RT mode activation. .. Caution:: - **Realtime monitoring and control are supported only on a real robot system.** This sample must be executed with a **real controller IP** (e.g. ``192.168.137.100``) and ``ROBOT_SYSTEM_REAL``. - **Only one Real-Time UDP client is allowed at a time.** If another application is already connected to the real-time UDP port, the robot will reject the new RT connection. .. figure:: /tutorials/images/tutorials/6_realtime_control_sample_caution.png :alt: 6_realtime_control_sample_caution :width: 100% :align: center .. raw:: html
This example is located in the following path: |br| ``API-DRFL/example/Linux_64/6_realtime_control_sample.cpp`` **See the example on the** `Doosan Robotics API-DRFL GitHub `_. **Watch the full walk-through on the** `Doosan Robotics Official Youtube `_. .. raw:: html
**Watch the real robot run on the** `Doosan Robotics Official Youtube `_. .. raw:: html
Setup & Global Variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The example defines a global DRFLEx controller instance along with several flags used to track: |br| **control authority** (GRANT), **TP initialization** status, **Stop/Pause/Resume** command states and internal RT trajectory parameters. .. code-block:: cpp CDRFLEx Drfl; bool g_bHasControlAuthority = false; bool g_TpInitailizingComplted = false; bool g_mStat = false; bool g_Stop = false; bool moving = false; Additionally, the example declares structures for quintic trajectory planning. .. code-block:: cpp struct PlanParam { float time; float ps[6], vs[6], as[6]; float pf[6], vf[6], af[6]; float A0[6], A1[6], A2[6], A3[6], A4[6], A5[6]; }; struct TraParam { float time; float pos[6], vel[6], acc[6]; }; They store start/end boundary conditions and the polynomial coefficients that define smooth RT motion. Callback Registration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Before opening the controller connection, the example registers all required callbacks. .. code-block:: cpp Drfl.set_on_homming_completed(OnHommingCompleted); Drfl.set_on_monitoring_data(OnMonitoringDataCB); Drfl.set_on_monitoring_data_ex(OnMonitoringDataExCB); Drfl.set_on_monitoring_access_control(OnMonitroingAccessControlCB); Drfl.set_on_tp_initializing_completed(OnTpInitializingCompleted); Drfl.set_on_log_alarm(OnLogAlarm); Drfl.set_on_tp_popup(OnTpPopup); Drfl.set_on_tp_log(OnTpLog); Drfl.set_on_tp_progress(OnTpProgress); Drfl.set_on_tp_get_user_input(OnTpGetuserInput); Drfl.set_on_rt_monitoring_data(OnRTMonitoringData); Drfl.set_on_program_stopped(OnProgramStopped); Drfl.set_on_disconnected(OnDisConnected); **Access Control Monitoring** .. code-block:: cpp void OnMonitroingAccessControlCB(const MONITORING_ACCESS_CONTROL eTrasnsitControl) { switch (eTrasnsitControl) { case MONITORING_ACCESS_CONTROL_REQUEST: assert(Drfl.ManageAccessControl(MANAGE_ACCESS_CONTROL_RESPONSE_NO)); break; case MONITORING_ACCESS_CONTROL_GRANT: g_bHasControlAuthority = true; break; case MONITORING_ACCESS_CONTROL_DENY: case MONITORING_ACCESS_CONTROL_LOSS: g_bHasControlAuthority = false; if (g_TpInitailizingComplted) { Drfl.ManageAccessControl(MANAGE_ACCESS_CONTROL_FORCE_REQUEST); } break; default: break; } } This callback updates GRANT/LOSS state when authority changes. **TP Initialization → Force Access Request** .. code-block:: cpp void OnTpInitializingCompleted() { g_TpInitailizingComplted = true; Drfl.ManageAccessControl(MANAGE_ACCESS_CONTROL_FORCE_REQUEST); } When the TP initializes, the program automatically sends a forced access request. Connection & Version Information ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ After callback registration, the controller connection is opened: .. code-block:: cpp assert(Drfl.open_connection("192.168.137.100")); SYSTEM_VERSION tSysVerion = {'\0'}; Drfl.get_system_version(&tSysVerion); assert(Drfl.setup_monitoring_version(1)); std::cout << "System version: " << tSysVerion._szController << std::endl; std::cout << "Library version: " << Drfl.get_library_version() << std::endl; Console example: .. code-block:: text System version: GF03050000 Library version: GL013300 Control Authority & Servo On ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Before any real-time control operations, the robot must: - GRANT API control authority - Reach **STATE_STANDBY** (Servo On complete) The sample repeatedly tries Servo On until both are satisfied. .. code-block:: cpp while (!g_bHasControlAuthority) { std::this_thread::sleep_for(std::chrono::milliseconds(1000)); Drfl.set_robot_control(CONTROL_SERVO_ON); } std::cout << "Robot state: " << Drfl.get_robot_state() << std::endl; std::cout << "API control authority: " << g_bHasControlAuthority << std::endl; Console example: .. code-block:: text Successfully Servo On !! Robot state: 1 API control authority: 1 Robot Mode Configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Real-time control requires: **AUTONOMOUS mode**, **REAL system**, **AUTONOMOUS safety mode**. .. code-block:: cpp assert(Drfl.set_robot_mode(ROBOT_MODE_AUTONOMOUS)); assert(Drfl.set_robot_system(ROBOT_SYSTEM_REAL)); RT Session Activation ~~~~~~~~~~~~~~~~~~~~~~~~~~~ When the user presses **`s`** in the main loop, the program starts the entire **real-time control sequence** at once. It connects the RT channel, configures RT output parameters, switches the robot to AUTONOMOUS + AUTONOMOUS safety mode, starts RT control, and finally creates a **high-priority real-time thread** that runs ``realtime_task``. |br| (connection → RT output configuration → RT start → thread creation) .. code-block:: cpp case 's': { // Start realtime control sequence (combines cases 1,2,3,4) std::cout << "Starting realtime control sequence..." << std::endl; // Step 1: Connect RT control Drfl.connect_rt_control("192.168.137.100"); std::cout << "RT control connected" << std::endl; // Step 2: Set RT control output std::string version = "v1.0"; float period = 0.001f; // 1 ms RT cycle int losscount = 4; // allowed packet loss count Drfl.set_rt_control_output(version, period, losscount); std::cout << "RT control output configured" << std::endl; std::this_thread::sleep_for(std::chrono::microseconds(1000)); // Step 3: Set robot mode and start RT control Drfl.set_robot_mode(ROBOT_MODE_AUTONOMOUS); Drfl.set_safety_mode(SAFETY_MODE_AUTONOMOUS, SAFETY_MODE_EVENT_MOVE); Drfl.start_rt_control(); std::cout << "RT control started" << std::endl; std::this_thread::sleep_for(std::chrono::microseconds(1000)); // Step 4: Create real-time thread (realtime_task) pthread_t rt_thread; pthread_attr_t attr; struct sched_param param; pthread_attr_init(&attr); pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedpolicy(&attr, SCHED_FIFO); // real-time scheduling policy param.sched_priority = 99; // high priority pthread_attr_setschedparam(&attr, ¶m); int v = pthread_create(&rt_thread, &attr, realtime_task, nullptr); if (v != 0) { perror("Failed to create real-time thread"); std::cout << "Error: " << v << std::endl; return 1; } std::cout << "Realtime thread created successfully" << std::endl; break; } 1. Open the real-time UDP channel using connect_rt_control. 2. Configure RT parameters (1 ms cycle time, allowed packet loss, etc.) using set_rt_control_output. 3. Switch the robot to ROBOT_MODE_AUTONOMOUS and SAFETY_MODE_AUTONOMOUS, then start the RT session with start_rt_control. 4. Configure a POSIX thread with the SCHED_FIFO real-time scheduling policy and priority 99, then create a dedicated real-time thread that runs realtime_task and streams servoj_rt commands at a 1 kHz rate. Console example: .. code-block:: text Starting realtime control sequence... RT control connected RT control output configured RT control started Realtime thread created successfully Stop / Pause / Resume ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ While the RT loop is running, another thread (``ThreadFunc``) can listen for keyboard input and send **Stop / Pause / Resume** commands to the controller. .. code-block:: cpp int linux_kbhit(void) { struct termios oldt, newt; int ch; tcgetattr(STDIN_FILENO, &oldt); newt = oldt; newt.c_lflag &= ~(ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &newt); ch = getchar(); tcsetattr(STDIN_FILENO, TCSANOW, &oldt); return ch; } uint32_t ThreadFunc(void* arg) { while (true) { if (linux_kbhit()) { char ch = getch(); switch (ch) { case 's': std::printf("Stop!\n"); g_Stop = true; Drfl.MoveStop(STOP_TYPE_SLOW); break; case 'p': std::printf("Pause!\n"); Drfl.MovePause(); break; case 'r': std::printf("Resume!\n"); Drfl.MoveResume(); break; } } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } return 0; } This allows the user to manually override robot motion during real-time execution (slow stop, pause, resume). Quintic Trajectory Planning ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Before entering the RT loop, the sample prepares two trajectories: |br| home → target_pose and target_pose → home ``TrajectoryPlan`` computes A0~A5 polynomial coefficients: .. code-block:: cpp // Motion parameters float target_time = 8.0; float posj_s[6] = {0, 0, 0, 0, 0, 0}; float posj_f[6] = {30, 30, 60, -30, -90, 30}; TraParam tra1, tra2; PlanParam plan1, plan2; plan1.time = target_time; plan2.time = target_time; memcpy(plan1.ps, posj_s, NUMBER_OF_JOINT * sizeof(float)); memcpy(plan1.pf, posj_f, NUMBER_OF_JOINT * sizeof(float)); memcpy(plan2.ps, posj_f, NUMBER_OF_JOINT * sizeof(float)); memcpy(plan2.pf, posj_s, NUMBER_OF_JOINT * sizeof(float)); float vs[6] = {0, 0, 0, 0, 0, 0}; float vf[6] = {0, 0, 0, 0, 0, 0}; float as[6] = {0, 0, 0, 0, 0, 0}; float af[6] = {0, 0, 0, 0, 0, 0}; memcpy(plan1.vs, vs, NUMBER_OF_JOINT * sizeof(float)); memcpy(plan1.vf, vf, NUMBER_OF_JOINT * sizeof(float)); memcpy(plan1.as, as, NUMBER_OF_JOINT * sizeof(float)); memcpy(plan1.af, af, NUMBER_OF_JOINT * sizeof(float)); memcpy(plan2.vs, vs, NUMBER_OF_JOINT * sizeof(float)); memcpy(plan2.vf, vf, NUMBER_OF_JOINT * sizeof(float)); memcpy(plan2.as, as, NUMBER_OF_JOINT * sizeof(float)); memcpy(plan2.af, af, NUMBER_OF_JOINT * sizeof(float)); TrajectoryPlan(&plan1); TrajectoryPlan(&plan2); These coefficients allow generating smooth 5th-order position/velocity/acceleration profiles. Realtime RT Loop ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The core logic of the real-time control example is implemented inside the **``realtime_task()``** function, which runs inside the high-priority RT thread. **Initialization: home position & safety mode** .. code-block:: cpp float home[6] = {0, 0, 0, 0, 0, 0}; float posj_s[6] = {0, 0, 0, 0, 0, 0}; float posj_f[6] = {30, 30, 60, -30, -90, 30}; const float None = -10000; // Set safety mode and move to home Drfl.movej(home, 60, 60); Drfl.set_safety_mode(SAFETY_MODE_AUTONOMOUS, SAFETY_MODE_EVENT_MOVE); The robot first moves to the home position, and the safety mode is set to AUTONOMOUS / EVENT_MOVE before any real-time streaming starts. **RT velocity/acceleration setup** .. code-block:: cpp float vel[6] = {100, 100, 100, 100, 100, 100}; float acc[6] = {120, 120, 120, 120, 120, 120}; Drfl.set_velj_rt(vel); Drfl.set_accj_rt(acc); These define the joint velocity/acceleration bounds for real-time motion commands. **Control loop timing parameters** .. code-block:: cpp const int loop_period_ms = 1; float dt = 0.001f * loop_period_ms; std::chrono::milliseconds loop_period(loop_period_ms); int repeat = 100000; float ratio = 15; double tt = dt * ratio; - ``loop_period_ms = 1`` → 1 ms control cycle (1 kHz loop). - ``tt`` is passed to ``servoj_rt`` as the trajectory time parameter. **Alternating forward/backward trajectories** The outer loop alternates between **forward (plan1)** and **backward (plan2)** trajectories: .. code-block:: cpp for (int i = 0; i < repeat; i++) { bool flag = true; int count = 0; float time = 0; PlanParam* current_plan = (i % 2 == 0) ? &plan1 : &plan2; TraParam* current_tra = (i % 2 == 0) ? &tra1 : &tra2; std::cout << "Loop " << i << ") Start position: "; for (size_t k = 0; k < 6; k++) { std::cout << current_plan->ps[k] << ", "; } std::cout << std::endl; while (flag) { auto loop_start_time = std::chrono::steady_clock::now(); time = (++count) * dt; current_tra->time = time; TrajectoryGenerator(current_plan, current_tra); // (body omitted for brevity) } } For each loop: - **even index (0, 2, 4, …)**: home → target (``plan1``) - **odd index (1, 3, 5, …)**: target → home (``plan2``) Example RT console logs: .. code-block:: text Loop 0) Start position: 0, 0, 0, 0, 0, 0, Loop 0) End position: 30, 30, 60, -30, -90, 30, Loop 1) Start position: 30, 30, 60, -30, -90, 30, Loop 1) End position: 0, 0, 0, 0, 0, 0, ... **Real-time servo command (``servoj_rt``)** Inside the inner while-loop, a trajectory sample is generated and sent to the controller. .. code-block:: cpp TrajectoryGenerator(current_plan, current_tra); if (i % 2 != 0) { // On the return path, ignore vel/acc (position-only control) for (int j = 0; j < 6; j++) { current_tra->vel[j] = None; current_tra->acc[j] = None; } } Drfl.servoj_rt(current_tra->pos, current_tra->vel, current_tra->acc, tt); - For **forward motion** (even loops), position/velocity/acceleration are sent. - For **return motion** (odd loops), velocity and acceleration are replaced with ``None`` (``-10000``), effectively switching to position-only RT control. When the current time exceeds the planned duration, the loop ends and the final pose is printed. .. code-block:: cpp if (time > current_plan->time) { flag = false; std::cout << "Loop " << i << ") End position: "; for (size_t k = 0; k < 6; k++) { std::cout << current_plan->pf[k] << ", "; } std::cout << std::endl; break; } **1 kHz timing enforcement** The loop measures the elapsed time and sleeps for the remaining period to maintain a constant 1 ms cycle. .. code-block:: cpp auto loop_end_time = std::chrono::steady_clock::now(); auto elapsed_time = std::chrono::duration_cast(loop_end_time - loop_start_time); if (elapsed_time < loop_period) { std::this_thread::sleep_for(loop_period - elapsed_time); } This enforces deterministic timing for the RT control loop. Stopping RT Control ~~~~~~~~~~~~~~~~~~~~~~~~~ Pressing ``e`` ends RT streaming. .. code-block:: cpp case 'e': Drfl.stop_rt_control(); Drfl.disconnect_rt_control(); break; When the user presses ``q``, the sample exits and all connections are closed. .. code-block:: cpp // Cleanup Drfl.disconnect_rt_control(); Drfl.CloseConnection();