1 /****************************************************************************
2  *
3  * ViSP, open source Visual Servoing Platform software.
4  * Copyright (C) 2005 - 2019 by Inria. All rights reserved.
5  *
6  * This software is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  * See the file LICENSE.txt at the root directory of this source
11  * distribution for additional information about the GNU GPL.
12  *
13  * For using ViSP with software that can not be combined with the GNU
14  * GPL, please contact Inria about acquiring a ViSP Professional
15  * Edition License.
16  *
17  * See http://visp.inria.fr for more information.
18  *
19  * This software was developed at:
20  * Inria Rennes - Bretagne Atlantique
21  * Campus Universitaire de Beaulieu
22  * 35042 Rennes Cedex
23  * France
24  *
25  * If you have questions regarding the use of this file, please contact
26  * Inria at visp@inria.fr
27  *
28  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
29  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
30  *
31  * Description:
32  * Interface for Flir Ptu Cpi robot.
33  *
34  * Authors:
35  * Fabien Spindler
36  *
37  *****************************************************************************/
38 
39 #include <visp3/core/vpConfig.h>
40 
41 #ifdef VISP_HAVE_FLIR_PTU_SDK
42 
43 #include <signal.h>
44 #include <stdexcept>
45 
46 extern "C" {
47 #include <cpi.h>
48 }
49 
50 /*!
51   \file vpRobotFlirPtu.cpp
52   \brief Interface for Flir Ptu Cpi robot.
53 */
54 
55 #include <visp3/core/vpHomogeneousMatrix.h>
56 #include <visp3/robot/vpRobotFlirPtu.h>
57 
58 /*!
59 
60   Emergency stops the robot if the program is interrupted by a SIGINT
61   (CTRL C), SIGSEGV (segmentation fault), SIGBUS (bus error), SIGKILL
62   or SIGQUIT signal.
63 
64 */
emergencyStop(int signo)65 void vpRobotFlirPtu::emergencyStop(int signo)
66 {
67   std::stringstream msg;
68   msg << "Stop the FLIR PTU by signal (" << signo << "): " << (char)7;
69   switch (signo) {
70   case SIGINT:
71     msg << "SIGINT (stop by ^C) ";
72     break;
73   case SIGSEGV:
74     msg << "SIGSEGV (stop due to a segmentation fault) ";
75     break;
76 #ifndef WIN32
77   case SIGBUS:
78     msg << "SIGBUS (stop due to a bus error) ";
79     break;
80   case SIGKILL:
81     msg << "SIGKILL (stop by CTRL \\) ";
82     break;
83   case SIGQUIT:
84     msg << "SIGQUIT ";
85     break;
86 #endif
87   default:
88     msg << signo << std::endl;
89   }
90 
91   throw vpRobotException(vpRobotException::signalException, msg.str());
92 }
93 
94 /*!
95   Basic initialization.
96  */
init()97 void vpRobotFlirPtu::init()
98 {
99   // If you want to control the robot in Cartesian in a tool frame, set the corresponding transformation in m_eMc
100   // that is set to identity by default in the constructor.
101 
102   maxRotationVelocity = maxRotationVelocityDefault;
103   maxTranslationVelocity = maxTranslationVelocityDefault;
104 
105   // Set here the robot degrees of freedom number
106   nDof = 2; // Flir Ptu has 2 dof
107 }
108 
109 /*!
110   Default constructor.
111  */
vpRobotFlirPtu()112 vpRobotFlirPtu::vpRobotFlirPtu()
113   : m_eMc(), m_cer(NULL), m_status(0), m_pos_max_tics(2), m_pos_min_tics(2), m_vel_max_tics(2), m_res(2),
114     m_connected(false), m_njoints(2), m_positioning_velocity(20.)
115 {
116   signal(SIGINT, vpRobotFlirPtu::emergencyStop);
117   signal(SIGSEGV, vpRobotFlirPtu::emergencyStop);
118 #ifndef WIN32
119   signal(SIGBUS, vpRobotFlirPtu::emergencyStop);
120   signal(SIGKILL, vpRobotFlirPtu::emergencyStop);
121   signal(SIGQUIT, vpRobotFlirPtu::emergencyStop);
122 #endif
123 
124   init();
125 }
126 
127 /*!
128   Destructor.
129  */
~vpRobotFlirPtu()130 vpRobotFlirPtu::~vpRobotFlirPtu()
131 {
132   stopMotion();
133   disconnect();
134 }
135 
136 /*
137   At least one of these function has to be implemented to control the robot with a
138   Cartesian velocity:
139   - get_eJe()
140   - get_fJe()
141 */
142 
143 /*!
144   Get the robot Jacobian expressed in the end-effector frame.
145   \return End-effector frame Jacobian.
146 
147   \sa get_fJe()
148 */
get_eJe()149 vpMatrix vpRobotFlirPtu::get_eJe()
150 {
151   vpMatrix eJe;
152   vpColVector q;
153   getPosition(vpRobot::JOINT_STATE, q);
154   eJe.resize(6, 2);
155   eJe = 0;
156   eJe[3][0] = -sin(q[1]);
157   eJe[4][1] = -1;
158   eJe[5][0] = -cos(q[1]);
159   return eJe;
160 }
161 
162 /*!
163   Get the robot Jacobian expressed in the end-effector frame.
164   \param[out] eJe : End-effector frame Jacobian.
165 
166   \sa get_fJe()
167 */
get_eJe(vpMatrix & eJe)168 void vpRobotFlirPtu::get_eJe(vpMatrix &eJe) { eJe = get_eJe(); }
169 
170 /*!
171   Get the robot Jacobian expressed in the robot reference frame.
172   \return Base (or reference) frame Jacobian.
173 
174   \sa get_eJe()
175 */
get_fJe()176 vpMatrix vpRobotFlirPtu::get_fJe()
177 {
178   vpMatrix fJe;
179   vpColVector q;
180   getPosition(vpRobot::JOINT_STATE, q);
181   fJe.resize(6, 2);
182   fJe = 0;
183   fJe[3][1] = -sin(q[1]);
184   fJe[4][1] = -cos(q[1]);
185   fJe[5][0] = -1;
186 
187   return fJe;
188 }
189 
190 /*!
191   Get the robot Jacobian expressed in the robot reference frame.
192   \param[out] fJe : Base (or reference) frame Jacobian.
193 
194   \sa get_fJe()
195 */
get_fJe(vpMatrix & fJe)196 void vpRobotFlirPtu::get_fJe(vpMatrix &fJe) { fJe = get_fJe(); }
197 
198 /*!
199   Get the robot geometric model corresponding to the homogeneous transformation between base (or reference) frame and
200   end-effector frame. \return Homogeneous transformation between base (or reference) frame and end-effector frame.
201 */
get_fMe()202 vpMatrix vpRobotFlirPtu::get_fMe()
203 {
204   vpHomogeneousMatrix fMe;
205   vpColVector q;
206   getPosition(vpRobot::JOINT_STATE, q);
207   double c1 = cos(q[0]);
208   double c2 = cos(q[1]);
209   double s1 = sin(q[0]);
210   double s2 = sin(q[1]);
211 
212   fMe[0][0] = c1 * c2;
213   fMe[0][1] = s1;
214   fMe[0][2] = -c1 * s2;
215 
216   fMe[1][0] = -s1 * c2;
217   fMe[1][1] = c1;
218   fMe[1][2] = s1 * s2;
219 
220   fMe[2][0] = s2;
221   fMe[2][1] = 0;
222   fMe[2][2] = c2;
223 
224   return fMe;
225 }
226 
227 /*!
228 
229   Return the velocity twist transformation matrix from camera frame to end-effector
230   frame.  This transformation allows to transform a velocity twist expressed
231   in the end-effector frame into the camera frame thanks to the homogeneous matrix
232   eMc set using set_eMc().
233 
234   \return Velocity twist transformation.
235 
236   \sa set_eMc(), get_eMc()
237 
238 */
get_cVe() const239 vpVelocityTwistMatrix vpRobotFlirPtu::get_cVe() const
240 {
241   vpVelocityTwistMatrix cVe;
242   cVe.buildFrom(m_eMc.inverse());
243 
244   return cVe;
245 }
246 
247 /*
248   At least one of these function has to be implemented to control the robot:
249   - setCartVelocity()
250   - setJointVelocity()
251 */
252 
253 /*!
254   Send to the controller a 6-dim velocity skew vector expressed in a Cartesian frame.
255   \param[in] frame : Cartesian control frame (either tool frame or end-effector) in which the velocity \e v is
256   expressed. Units are m/s for translation and rad/s for rotation velocities. \param[in] v : 6-dim vector that contains
257   the 6 components of the velocity skew to send to the robot. Units are m/s and rad/s.
258 */
setCartVelocity(const vpRobot::vpControlFrameType frame,const vpColVector & v)259 void vpRobotFlirPtu::setCartVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &v)
260 {
261   if (v.size() != 6) {
262     throw(vpException(vpException::fatalError,
263                       "Cannot send a velocity-skew vector in tool frame that is not 6-dim (%d)", v.size()));
264   }
265 
266   vpColVector v_e; // This is the velocity that the robot is able to apply in the end-effector frame
267   switch (frame) {
268   case vpRobot::TOOL_FRAME: {
269     // We have to transform the requested velocity in the end-effector frame.
270     // Knowing that the constant transformation between the tool frame and the end-effector frame obtained
271     // by extrinsic calibration is set in m_eMc we can compute the velocity twist matrix eVc that transform
272     // a velocity skew from tool (or camera) frame into end-effector frame
273     vpVelocityTwistMatrix eVc(m_eMc);
274     v_e = eVc * v;
275     break;
276   }
277 
278   case vpRobot::END_EFFECTOR_FRAME:
279   case vpRobot::REFERENCE_FRAME: {
280     v_e = v;
281     break;
282   }
283   case vpRobot::JOINT_STATE:
284   case vpRobot::MIXT_FRAME:
285     // Out of the scope
286     break;
287   }
288 
289   // Implement your stuff here to send the end-effector velocity skew v_e
290   // - If the SDK allows to send cartesian velocities in the end-effector, it's done. Just wrap data in v_e
291   // - If the SDK allows to send cartesian velocities in the reference (or base) frame you have to implement
292   //   the robot Jacobian in set_fJe() and call:
293   //   vpColVector v = get_fJe().inverse() * v_e;
294   //   At this point you have to wrap data in v that is the 6-dim velocity to apply to the robot
295   // - If the SDK allows to send only joint velocities you have to implement the robot Jacobian in set_eJe()
296   //   and call:
297   //   vpColVector qdot = get_eJe().inverse() * v_e;
298   //   setJointVelocity(qdot);
299   // - If the SDK allows to send only a cartesian position trajectory of the end-effector position in the base frame
300   //   called fMe (for fix frame to end-effector homogeneous transformation) you can transform the cartesian
301   //   velocity in the end-effector into a displacement eMe using the exponetial map:
302   //   double delta_t = 0.010; // in sec
303   //   vpHomogenesousMatrix eMe = vpExponentialMap::direct(v_e, delta_t);
304   //   vpHomogenesousMatrix fMe = getPosition(vpRobot::REFERENCE_FRAME);
305   //   the new position to reach is than given by fMe * eMe
306   //   vpColVector fpe(vpPoseVector(fMe * eMe));
307   //   setPosition(vpRobot::REFERENCE_FRAME, fpe);
308 
309   std::cout << "Not implemented ! " << std::endl;
310   std::cout << "To implement me you need : " << std::endl;
311   std::cout << "\t to known the robot jacobian expressed in ";
312   std::cout << "the end-effector frame (eJe) " << std::endl;
313   std::cout << "\t the frame transformation  between tool or camera frame ";
314   std::cout << "and end-effector frame (cMe)" << std::endl;
315 }
316 
317 /*!
318   Send a joint velocity to the controller.
319   \param[in] qdot : Joint velocities vector. Units are rad/s for a robot arm.
320  */
setJointVelocity(const vpColVector & qdot)321 void vpRobotFlirPtu::setJointVelocity(const vpColVector &qdot)
322 {
323   if (!m_connected) {
324     disconnect();
325     throw(vpException(vpException::fatalError, "FLIR PTU is not connected."));
326   }
327 
328   std::vector<int> vel_tics(2);
329 
330   for (int i = 0; i < 2; i++) {
331     vel_tics[i] = rad2tics(i, qdot[i]);
332     if (std::fabs(vel_tics[i]) > m_vel_max_tics[i]) {
333       disconnect();
334       throw(vpException(vpException::fatalError, "Cannot set joint %d velocity %f (deg/s). Out of limits [-%f, %f].", i,
335                         vpMath::deg(qdot[i]), -tics2deg(i, m_vel_max_tics[i]), tics2deg(i, m_vel_max_tics[i])));
336     }
337   }
338 
339   if (cpi_ptcmd(m_cer, &m_status, OP_PAN_DESIRED_SPEED_SET, vel_tics[0]) ||
340       cpi_ptcmd(m_cer, &m_status, OP_TILT_DESIRED_SPEED_SET, vel_tics[1])) {
341     throw(vpException(vpException::fatalError, "Unable to set velocity."));
342   }
343 }
344 
345 /*!
346   Send to the controller a velocity in a given frame.
347   \param[in] frame : Control frame in which the velocity \e vel is expressed.
348   Velocities could be joint velocities, or cartesian velocities. Units are m/s for translation and
349   rad/s for rotation velocities.
350   \param[in] vel : Vector that contains the velocity to apply to the robot.
351  */
setVelocity(const vpRobot::vpControlFrameType frame,const vpColVector & vel)352 void vpRobotFlirPtu::setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &vel)
353 {
354   if (vpRobot::STATE_VELOCITY_CONTROL != getRobotState()) {
355     throw vpRobotException(vpRobotException::wrongStateError,
356                            "Cannot send a velocity to the robot. "
357                            "Call setRobotState(vpRobot::STATE_VELOCITY_CONTROL) once before "
358                            "entering your control loop.");
359   }
360 
361   vpColVector vel_sat(6);
362 
363   // Velocity saturation
364   switch (frame) {
365   // Saturation in cartesian space
366   case vpRobot::TOOL_FRAME:
367   case vpRobot::REFERENCE_FRAME:
368   case vpRobot::END_EFFECTOR_FRAME:
369   case vpRobot::MIXT_FRAME: {
370     if (vel.size() != 6) {
371       throw(vpException(vpException::dimensionError,
372                         "Cannot apply a Cartesian velocity that is not a 6-dim vector (%d)", vel.size()));
373     }
374     vpColVector vel_max(6);
375 
376     for (unsigned int i = 0; i < 3; i++)
377       vel_max[i] = getMaxTranslationVelocity();
378     for (unsigned int i = 3; i < 6; i++)
379       vel_max[i] = getMaxRotationVelocity();
380 
381     vel_sat = vpRobot::saturateVelocities(vel, vel_max, true);
382 
383     setCartVelocity(frame, vel_sat);
384     break;
385   }
386     // Saturation in joint space
387   case vpRobot::JOINT_STATE: {
388     if (vel.size() != static_cast<size_t>(nDof)) {
389       throw(vpException(vpException::dimensionError, "Cannot apply a joint velocity that is not a %d-dim vector (%d)",
390                         nDof, vel.size()));
391     }
392     vpColVector vel_max(vel.size());
393 
394     // Since the robot has only rotation axis all the joint max velocities are set to getMaxRotationVelocity()
395     vel_max = getMaxRotationVelocity();
396 
397     vel_sat = vpRobot::saturateVelocities(vel, vel_max, true);
398 
399     setJointVelocity(vel_sat);
400   }
401   }
402 }
403 
404 /*
405   THESE FUNCTIONS ARE NOT MENDATORY BUT ARE USUALLY USEFUL
406 */
407 
408 /*!
409   Get robot joint positions.
410   \param[in] q : Joint position as a 2-dim vector [pan, tilt] with values in radians.
411  */
getJointPosition(vpColVector & q)412 void vpRobotFlirPtu::getJointPosition(vpColVector &q)
413 {
414   if (!m_connected) {
415     disconnect();
416     throw(vpException(vpException::fatalError, "FLIR PTU is not connected."));
417   }
418 
419   std::vector<int> pos_tics(2);
420 
421   if (cpi_ptcmd(m_cer, &m_status, OP_PAN_CURRENT_POS_GET, &pos_tics[0])) {
422     disconnect();
423     throw(vpException(vpException::fatalError, "Unable to query pan position."));
424   }
425   if (cpi_ptcmd(m_cer, &m_status, OP_TILT_CURRENT_POS_GET, &pos_tics[1])) {
426     disconnect();
427     throw(vpException(vpException::fatalError, "Unable to query pan position."));
428   }
429 
430   q.resize(2);
431   for (int i = 0; i < 2; i++) {
432     q[i] = tics2rad(i, pos_tics[i]);
433   }
434 }
435 
436 /*!
437   Get robot position.
438   \param[in] frame : Considered cartesian frame or joint state.
439   \param[out] q : Position of the arm.
440  */
getPosition(const vpRobot::vpControlFrameType frame,vpColVector & q)441 void vpRobotFlirPtu::getPosition(const vpRobot::vpControlFrameType frame, vpColVector &q)
442 {
443   if (frame == JOINT_STATE) {
444     getJointPosition(q);
445   } else {
446     std::cout << "Not implemented ! " << std::endl;
447   }
448 }
449 
450 /*!
451   Set a position to reach.
452   \param[in] frame : Considered cartesian frame or joint state.
453   \param[in] q : Position to reach.
454  */
setPosition(const vpRobot::vpControlFrameType frame,const vpColVector & q)455 void vpRobotFlirPtu::setPosition(const vpRobot::vpControlFrameType frame, const vpColVector &q)
456 {
457   if (frame != vpRobot::JOINT_STATE) {
458     std::cout << "FLIR PTU positioning is not implemented in this frame" << std::endl;
459     return;
460   }
461 
462   if (q.size() != 2) {
463     disconnect();
464     throw(vpException(vpException::fatalError, "FLIR PTU has only %d joints. Cannot set a position that is %d-dim.",
465                       m_njoints, q.size()));
466   }
467   if (!m_connected) {
468     disconnect();
469     throw(vpException(vpException::fatalError, "FLIR PTU is not connected."));
470   }
471 
472   double vmin = 0.01, vmax = 100.;
473   if (m_positioning_velocity < vmin || m_positioning_velocity > vmax) {
474     disconnect();
475     throw(
476         vpException(vpException::fatalError, "FLIR PTU Positioning velocity %f is not in range [%f, %f]", vmin, vmax));
477   }
478 
479   std::vector<int> pos_tics(2);
480 
481   for (int i = 0; i < 2; i++) {
482     pos_tics[i] = rad2tics(i, q[i]);
483     if (pos_tics[i] < m_pos_min_tics[i] || pos_tics[i] > m_pos_max_tics[i]) {
484       disconnect();
485       throw(vpException(vpException::fatalError, "Cannot set joint %d position %f (deg). Out of limits [%f, %f].", i,
486                         vpMath::deg(q[i]), tics2deg(i, m_pos_min_tics[i]), tics2deg(i, m_pos_max_tics[i])));
487     }
488   }
489 
490   // Set desired speed wrt max pan/tilt speed
491   if (cpi_ptcmd(m_cer, &m_status, OP_PAN_DESIRED_SPEED_SET, (int)(m_vel_max_tics[0] * m_positioning_velocity / 100.)) ||
492       cpi_ptcmd(m_cer, &m_status, OP_TILT_DESIRED_SPEED_SET,
493                 (int)(m_vel_max_tics[1] * m_positioning_velocity / 100.))) {
494     disconnect();
495     throw(vpException(vpException::fatalError, "Setting FLIR pan/tilt positioning velocity failed"));
496   }
497 
498   if (cpi_ptcmd(m_cer, &m_status, OP_PAN_DESIRED_POS_SET, pos_tics[0]) ||
499       cpi_ptcmd(m_cer, &m_status, OP_TILT_DESIRED_POS_SET, pos_tics[1])) {
500     disconnect();
501     throw(vpException(vpException::fatalError, "FLIR PTU failed to go to position %d, %d (deg).", vpMath::deg(q[0]),
502                       vpMath::deg(q[1])));
503   }
504 
505   if (cpi_block_until(m_cer, NULL, NULL, OP_PAN_CURRENT_POS_GET, pos_tics[0]) ||
506       cpi_block_until(m_cer, NULL, NULL, OP_TILT_CURRENT_POS_GET, pos_tics[1])) {
507     disconnect();
508     throw(vpException(vpException::fatalError, "FLIR PTU failed to wait until position %d, %d reached (deg)",
509                       vpMath::deg(q[0]), vpMath::deg(q[1])));
510   }
511 }
512 
513 /*!
514   Get a displacement.
515   \param[in] frame : Considered cartesian frame or joint state.
516   \param[out] q : Displacement in meter and rad.
517  */
getDisplacement(const vpRobot::vpControlFrameType frame,vpColVector & q)518 void vpRobotFlirPtu::getDisplacement(const vpRobot::vpControlFrameType frame, vpColVector &q)
519 {
520   (void)frame;
521   (void)q;
522   std::cout << "Not implemented ! " << std::endl;
523 }
524 
525 /*!
526   Connect to FLIR PTU.
527   \param[in] portname : Connect to serial/socket.
528   \param[in] baudrate : Use baud rate (default: 9600).
529   \sa disconnect()
530  */
connect(const std::string & portname,int baudrate)531 void vpRobotFlirPtu::connect(const std::string &portname, int baudrate)
532 {
533   char errstr[128];
534 
535   if (m_connected) {
536     disconnect();
537   }
538 
539   if (portname.empty()) {
540     disconnect();
541     throw(vpException(vpException::fatalError, "Port name is required to connect to FLIR PTU."));
542   }
543 
544   if ((m_cer = (struct cerial *)malloc(sizeof(struct cerial))) == NULL) {
545     disconnect();
546     throw(vpException(vpException::fatalError, "Out of memory during FLIR PTU connection."));
547   }
548 
549   // Open a port
550   if (ceropen(m_cer, portname.c_str(), 0)) {
551 #if WIN32
552     throw(vpException(vpException::fatalError, "Failed to open %s: %s.", portname.c_str(),
553                       cerstrerror(m_cer, errstr, sizeof(errstr))));
554 #else
555     throw(vpException(vpException::fatalError, "Failed to open %s: %s.\nRun `sudo chmod a+rw %s`", portname.c_str(),
556                       cerstrerror(m_cer, errstr, sizeof(errstr)), portname.c_str()));
557 #endif
558   }
559 
560   // Set baudrate
561   // ignore errors since not all devices are serial ports
562   cerioctl(m_cer, CERIAL_IOCTL_BAUDRATE_SET, &baudrate);
563 
564   // Flush any characters already buffered
565   cerioctl(m_cer, CERIAL_IOCTL_FLUSH_INPUT, NULL);
566 
567   // Set two second timeout */
568   int timeout = 2000;
569   if (cerioctl(m_cer, CERIAL_IOCTL_TIMEOUT_SET, &timeout)) {
570     disconnect();
571     throw(vpException(vpException::fatalError, "cerial: timeout ioctl not supported."));
572   }
573 
574   // Sync and lock
575   int trial = 0;
576   do {
577     trial++;
578   } while (trial <= 3 && (cpi_resync(m_cer) || cpi_ptcmd(m_cer, &m_status, OP_NOOP)));
579   if (trial > 3) {
580     disconnect();
581     throw(vpException(vpException::fatalError, "Cannot communicate with FLIR PTU."));
582   }
583 
584   // Immediately execute commands (slave mode should be opt-in)
585   int rc;
586   if ((rc = cpi_ptcmd(m_cer, &m_status, OP_EXEC_MODE_SET, (cpi_enum)CPI_IMMEDIATE_MODE))) {
587     disconnect();
588     throw(vpException(vpException::fatalError, "Set Immediate Mode failed: %s", cpi_strerror(rc)));
589   }
590 
591   m_connected = true;
592 
593   getLimits();
594 }
595 
596 /*!
597   Close connection to PTU.
598   \sa connect()
599  */
disconnect()600 void vpRobotFlirPtu::disconnect()
601 {
602   if (m_cer != NULL) {
603     cerclose(m_cer);
604     free(m_cer);
605     m_cer = NULL;
606     m_connected = false;
607   }
608 }
609 
610 /*!
611  Read min/max position and speed.
612 */
getLimits()613 void vpRobotFlirPtu::getLimits()
614 {
615   if (!m_connected) {
616     disconnect();
617     throw(vpException(vpException::fatalError, "FLIR PTU is not connected."));
618   }
619 
620   int status;
621 
622   if ((status = cpi_ptcmd(m_cer, &m_status, OP_PAN_MAX_POSITION, &m_pos_max_tics[0])) ||
623       (status = cpi_ptcmd(m_cer, &m_status, OP_PAN_MIN_POSITION, &m_pos_min_tics[0])) ||
624       (status = cpi_ptcmd(m_cer, &m_status, OP_TILT_MAX_POSITION, &m_pos_max_tics[1])) ||
625       (status = cpi_ptcmd(m_cer, &m_status, OP_TILT_MIN_POSITION, &m_pos_min_tics[1])) ||
626       (status = cpi_ptcmd(m_cer, &m_status, OP_PAN_UPPER_SPEED_LIMIT_GET, &m_vel_max_tics[0])) ||
627       (status = cpi_ptcmd(m_cer, &m_status, OP_TILT_UPPER_SPEED_LIMIT_GET, &m_vel_max_tics[1]))) {
628     disconnect();
629     throw(vpException(vpException::fatalError, "Failed to get limits (%d) %s.", status, cpi_strerror(status)));
630   }
631 
632   // Get the ptu resolution so we can convert the angles to ptu positions
633   if ((status = cpi_ptcmd(m_cer, &m_status, OP_PAN_RESOLUTION, &m_res[0])) ||
634       (status = cpi_ptcmd(m_cer, &m_status, OP_TILT_RESOLUTION, &m_res[1]))) {
635     disconnect();
636     throw(vpException(vpException::fatalError, "Failed to get resolution (%d) %s.", status, cpi_strerror(status)));
637   }
638 
639   for (size_t i = 0; i < 2; i++) {
640     m_res[i] /= 3600.; // Resolutions are in arc-seconds, but we want degrees
641   }
642 }
643 
644 /*!
645   Return pan axis min and max position limits in radians as a 2-dim vector, with first value the pan min position and
646   second value, the pan max position.
647 
648   \sa getTiltPosLimits(), getPanTiltVelMax(), setPanPosLimits()
649 */
getPanPosLimits()650 vpColVector vpRobotFlirPtu::getPanPosLimits()
651 {
652   if (!m_connected) {
653     disconnect();
654     throw(vpException(vpException::fatalError, "FLIR PTU is not connected."));
655   }
656   vpColVector pan_pos_limits(2);
657   pan_pos_limits[0] = tics2rad(0, m_pos_min_tics[0]);
658   pan_pos_limits[1] = tics2rad(0, m_pos_max_tics[0]);
659 
660   return pan_pos_limits;
661 }
662 
663 /*!
664   Return tilt axis min and max position limits in radians as a 2-dim vector, with first value the tilt min position and
665   second value, the tilt max position.
666 
667   \sa getPanPosLimits(), getPanTiltVelMax(), setTiltPosLimits()
668  */
getTiltPosLimits()669 vpColVector vpRobotFlirPtu::getTiltPosLimits()
670 {
671   if (!m_connected) {
672     disconnect();
673     throw(vpException(vpException::fatalError, "FLIR PTU is not connected."));
674   }
675   vpColVector tilt_pos_limits(2);
676   tilt_pos_limits[0] = tics2rad(0, m_pos_min_tics[1]);
677   tilt_pos_limits[1] = tics2rad(0, m_pos_max_tics[1]);
678 
679   return tilt_pos_limits;
680 }
681 
682 /*!
683   Return pan/tilt axis max velocity in rad/s as a 2-dim vector, with first value the pan max velocity and second value,
684   the max tilt velocity.
685 
686   \sa getPanPosLimits(), getTiltPosLimits()
687  */
getPanTiltVelMax()688 vpColVector vpRobotFlirPtu::getPanTiltVelMax()
689 {
690   if (!m_connected) {
691     disconnect();
692     throw(vpException(vpException::fatalError, "FLIR PTU is not connected."));
693   }
694   vpColVector vel_max(2);
695   for (int i = 0; i < 2; i++) {
696     vel_max[i] = tics2rad(i, m_vel_max_tics[i]);
697   }
698   return vel_max;
699 }
700 
701 /*!
702    Modify pan position limit.
703    \param pan_limits : 2-dim vector that contains pan min and max limits in rad.
704 
705    \sa getPanPosLimits()
706  */
setPanPosLimits(const vpColVector & pan_limits)707 void vpRobotFlirPtu::setPanPosLimits(const vpColVector &pan_limits)
708 {
709   if (!m_connected) {
710     disconnect();
711     throw(vpException(vpException::fatalError, "FLIR PTU is not connected."));
712   }
713   if (pan_limits.size() != 2) {
714     disconnect();
715     throw(vpException(vpException::fatalError, "Cannot set max position that is not a 2-dim vector."));
716   }
717   std::vector<int> pan_limits_tics(2);
718   for (int i = 0; i < 2; i++) {
719     pan_limits_tics[i] = rad2tics(i, pan_limits[i]);
720   }
721 
722   int status;
723   if ((status = cpi_ptcmd(m_cer, &m_status, OP_PAN_USER_MIN_POS_SET, pan_limits_tics[0])) ||
724       (status = cpi_ptcmd(m_cer, &m_status, OP_PAN_USER_MAX_POS_SET, pan_limits_tics[1]))) {
725     disconnect();
726     throw(vpException(vpException::fatalError, "Failed to set pan position limits (%d) %s.", status,
727                       cpi_strerror(status)));
728   }
729 }
730 
731 /*!
732    Modify tilt position limit.
733    \param tilt_limits : 2-dim vector that contains tilt min and max limits in rad.
734 
735    \sa getPanPosLimits()
736  */
setTiltPosLimits(const vpColVector & tilt_limits)737 void vpRobotFlirPtu::setTiltPosLimits(const vpColVector &tilt_limits)
738 {
739   if (!m_connected) {
740     disconnect();
741     throw(vpException(vpException::fatalError, "FLIR PTU is not connected."));
742   }
743   if (tilt_limits.size() != 2) {
744     disconnect();
745     throw(vpException(vpException::fatalError, "Cannot set max position that is not a 2-dim vector."));
746   }
747   std::vector<int> tilt_limits_tics(2);
748   for (int i = 0; i < 2; i++) {
749     tilt_limits_tics[i] = rad2tics(i, tilt_limits[i]);
750   }
751 
752   int status;
753   if ((status = cpi_ptcmd(m_cer, &m_status, OP_TILT_USER_MIN_POS_SET, tilt_limits_tics[0])) ||
754       (status = cpi_ptcmd(m_cer, &m_status, OP_TILT_USER_MAX_POS_SET, tilt_limits_tics[1]))) {
755     disconnect();
756     throw(vpException(vpException::fatalError, "Failed to set tilt position limits (%d) %s.", status,
757                       cpi_strerror(status)));
758   }
759 }
760 
761 /*!
762 
763   Set the velocity used for a position control.
764 
765   \param velocity : Velocity in % of the maximum velocity between [0, 100]. Default value is 20.
766 */
setPositioningVelocity(double velocity)767 void vpRobotFlirPtu::setPositioningVelocity(double velocity) { m_positioning_velocity = velocity; }
768 
769 /*!
770 
771   Change the robot state.
772 
773   \param newState : New requested robot state if the robot is connected. If the robot is not connected, we return the
774   current state.
775 */
setRobotState(vpRobot::vpRobotStateType newState)776 vpRobot::vpRobotStateType vpRobotFlirPtu::setRobotState(vpRobot::vpRobotStateType newState)
777 {
778   if (!m_connected) {
779     return getRobotState();
780   }
781 
782   switch (newState) {
783   case vpRobot::STATE_STOP: {
784     // Start primitive STOP only if the current state is Velocity
785     if (vpRobot::STATE_VELOCITY_CONTROL == getRobotState()) {
786       stopMotion();
787 
788       // Set the PTU to pure velocity mode
789       if (cpi_ptcmd(m_cer, &m_status, OP_SPEED_CONTROL_MODE_SET, (cpi_enum)CPI_CONTROL_INDEPENDENT)) {
790         throw(vpException(vpException::fatalError, "Unable to set control mode independent."));
791       }
792     }
793     break;
794   }
795   case vpRobot::STATE_POSITION_CONTROL: {
796     if (vpRobot::STATE_VELOCITY_CONTROL == getRobotState()) {
797       std::cout << "Change the control mode from velocity to position control.\n";
798       stopMotion();
799 
800       // Set the PTU to pure velocity mode
801       if (cpi_ptcmd(m_cer, &m_status, OP_SPEED_CONTROL_MODE_SET, (cpi_enum)CPI_CONTROL_INDEPENDENT)) {
802         throw(vpException(vpException::fatalError, "Unable to set control mode independent."));
803       }
804 
805     } else {
806       // std::cout << "Change the control mode from stop to position
807       // control.\n";
808     }
809     break;
810   }
811   case vpRobot::STATE_VELOCITY_CONTROL: {
812     if (vpRobot::STATE_VELOCITY_CONTROL != getRobotState()) {
813       std::cout << "Change the control mode from stop to velocity control.\n";
814 
815       // Set the PTU to pure velocity mode
816       if (cpi_ptcmd(m_cer, &m_status, OP_SPEED_CONTROL_MODE_SET, (cpi_enum)CPI_CONTROL_PURE_VELOCITY)) {
817         throw(vpException(vpException::fatalError, "Unable to set velocity control mode."));
818       }
819     }
820     break;
821   }
822   default:
823     break;
824   }
825 
826   return vpRobot::setRobotState(newState);
827 }
828 
829 /*!
830   Reset PTU axis.
831  */
reset()832 void vpRobotFlirPtu::reset()
833 {
834   if (!m_connected) {
835     return;
836   }
837 
838   if (cpi_ptcmd(m_cer, &m_status, OP_RESET, NULL)) {
839     throw(vpException(vpException::fatalError, "Unable to reset PTU."));
840   }
841 }
842 
843 /*!
844   Stop PTU motion in velocity control mode.
845  */
stopMotion()846 void vpRobotFlirPtu::stopMotion()
847 {
848   if (vpRobot::STATE_VELOCITY_CONTROL != getRobotState()) {
849     return;
850   }
851 
852   if (!m_connected) {
853     return;
854   }
855 
856   if (cpi_ptcmd(m_cer, &m_status, OP_HALT, NULL)) {
857     throw(vpException(vpException::fatalError, "Unable to stop PTU."));
858   }
859 }
860 
861 /*!
862    When connected to the PTU by serial, get the PTU network IP address.
863 
864    \sa getNetworkGateway()
865  */
getNetworkIP()866 std::string vpRobotFlirPtu::getNetworkIP()
867 {
868   if (!m_connected) {
869     disconnect();
870     throw(vpException(vpException::fatalError, "FLIR PTU is not connected."));
871   }
872 
873   char str[64];
874   if (cpi_ptcmd(m_cer, &m_status, OP_NET_IP_GET, (int)sizeof(str), NULL, &str)) {
875     throw(vpException(vpException::fatalError, "Unable to get Network IP."));
876   }
877 
878   return (std::string(str));
879 }
880 
881 /*!
882    When connected to the PTU by serial, get the PTU network gateway.
883 
884    \sa getNetworkIP()
885  */
getNetworkGateway()886 std::string vpRobotFlirPtu::getNetworkGateway()
887 {
888   if (!m_connected) {
889     disconnect();
890     throw(vpException(vpException::fatalError, "FLIR PTU is not connected."));
891   }
892 
893   char str[64];
894   if (cpi_ptcmd(m_cer, &m_status, OP_NET_GATEWAY_GET, (int)sizeof(str), NULL, &str)) {
895     throw(vpException(vpException::fatalError, "Unable to get Network Gateway."));
896   }
897 
898   return (std::string(str));
899 }
900 
901 /*!
902    When connected to the PTU, get the PTU network hostname.
903 
904    \sa getNetworkIP()
905  */
getNetworkHostName()906 std::string vpRobotFlirPtu::getNetworkHostName()
907 {
908   if (!m_connected) {
909     disconnect();
910     throw(vpException(vpException::fatalError, "FLIR PTU is not connected."));
911   }
912 
913   char str[64];
914   if (cpi_ptcmd(m_cer, &m_status, OP_NET_HOSTNAME_GET, (int)sizeof(str), NULL, &str)) {
915     throw(vpException(vpException::fatalError, "Unable to get Network hostname."));
916   }
917 
918   return (std::string(str));
919 }
920 
921 /*!
922    Converts the angle from radian to robot position units using axis i resolution.
923    \param rad : Angle in radian.
924    \param i : Axis to consider, with 0 for pan and 1 for tilt axis.
925    \returns The position in robot units using axis resolution.
926 
927    \sa pos2rad()
928  */
rad2tics(int axis,double rad)929 int vpRobotFlirPtu::rad2tics(int axis, double rad) { return (static_cast<int>(vpMath::deg(rad) / m_res[axis])); }
930 
931 /*!
932    Converts the angle from robot position units into degrees using axis i resolution.
933    \param tics : Position in robot units.
934    \param i : Axis, with 0 for pan and 1 for tilt axis.
935    \returns The angle in degrees using axis resolution.
936 
937    \sa pos2rad()
938  */
tics2deg(int axis,int tics)939 double vpRobotFlirPtu::tics2deg(int axis, int tics) { return (tics * m_res[axis]); }
940 
941 /*!
942    Converts the angle from robot position units into radian using axis i resolution.
943    \param tics : Position in robot units.
944    \param i : Axis, with 0 for pan and 1 for tilt axis.
945    \returns The angle in radian using axis resolution.
946 
947    \sa pos2rad()
948  */
tics2rad(int axis,int tics)949 double vpRobotFlirPtu::tics2rad(int axis, int tics) { return vpMath::rad(tics2deg(axis, tics)); }
950 
951 #elif !defined(VISP_BUILD_SHARED_LIBS)
952 // Work arround to avoid warning: libvisp_robot.a(vpRobotFlirPtu.cpp.o) has
953 // no symbols
dummy_vpRobotFlirPtu()954 void dummy_vpRobotFlirPtu(){};
955 #endif
956