Instrument Neutral Distributed Interface INDI  2.0.2
integra.cpp
Go to the documentation of this file.
1 /*
2  Gemini Telescope Design Integra85 Focusing Rotator.
3  Copyright (C) 2017-2021 Hans Lambermont
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Lesser General Public
7  License as published by the Free Software Foundation; either
8  version 2.1 of the License, or (at your option) any later version.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Lesser General Public License for more details.
14 
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, write to the Free Software
17  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19 
20 #include "integra.h"
21 
22 #include "indicom.h"
24 
25 #include <cmath>
26 #include <cstring>
27 #include <memory>
28 #include <termios.h>
29 
30 #define INTEGRA_TIMEOUT_IN_S 5
31 #define INTEGRA_TEMPERATURE_LOOP_SKIPS 60
32 #define INTEGRA_TEMPERATURE_TRESHOLD_IN_C 0.1
33 #define INTEGRA_ROUNDING_FUDGE 0.001
34 
35 #define ROTATOR_TAB "Rotator"
36 #define SETTINGS_TAB "Settings"
37 
38 std::unique_ptr<Integra> integra(new Integra());
39 
40 typedef struct COMMANDDESC
41 {
42  const char cmd[12];
43  char ret[2][3];
45 
46 static const COMMANDDESC IntegraProtocol[] =
47 {
48  { "@SW%d,0\r\n", { "S", "SW"}},
49  { "@CS%d,0\r\n", { "C", "CS"}},
50  { "@CE%d,0\r\n", { "CE", "CE"}},
51  { "@CR%d,0\r\n", { "CR", "CR"}},
52  { "@TR\r\n", { "T", "TR"}},
53  { "@PW%d,0\r\n", { "P", "PW"}},
54  { "@PR%d,0\r\n", { "P", "PR"}},
55  { "@MI%d,%d\r\n", { "M", "MI"}},
56  { "@MO%d,%d\r\n", { "M", "MO"}},
57  { "@RR%d,0\r\n", { "R", "RR"}},
58  { "X\r\n", { "", "X"}},
59  { "@IW%d,0\r\n", { "I", "IW"}},
60  { "@ZW\r\n", { "", "ZW"}}
61 };
62 
63 enum
64 {
78 };
79 
81 {
84 
86  setVersion(1, 1);
87 }
88 
90 {
92 
93  IUFillNumber(&MaxPositionN[0], "FOCUSER", "Focuser", "%.f",
95  IUFillNumber(&MaxPositionN[1], "ROTATOR", "Rotator", "%.f",
97  IUFillNumberVector(&MaxPositionNP, MaxPositionN, 2, getDeviceName(), "MAX_POSITION", "Max position",
99 
100  FocusSpeedN[0].min = 1;
101  FocusSpeedN[0].max = 1;
102  FocusSpeedN[0].value = 1;
103 
104  // Temperature Sensor
105  IUFillNumber(&SensorN[SENSOR_TEMPERATURE], "TEMPERATURE", "Temperature (C)", "%.2f", -100, 100., 1., 0.);
106  IUFillNumberVector(&SensorNP, SensorN, 1, getDeviceName(), "SENSORS", "Sensors", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE );
107 
108  // Home Find
109  IUFillSwitch(&FindHomeS[HOMING_IDLE], "HOMING_IDLE", "Idle", ISS_ON);
110  IUFillSwitch(&FindHomeS[HOMING_START], "HOMING_START", "Start", ISS_OFF);
111  IUFillSwitch(&FindHomeS[HOMING_ABORT], "HOMING_ABORT", "Abort", ISS_OFF);
112  IUFillSwitchVector(&FindHomeSP, FindHomeS, HOMING_COUNT, getDeviceName(), "HOMING", "Home at Center", SETTINGS_TAB, IP_RW,
113  ISR_1OFMANY, 60, IPS_IDLE);
114 
115  // Relative and absolute movement
116  FocusAbsPosN[0].min = 0;
117  FocusAbsPosN[0].max = MaxPositionN[0].value;
118  FocusAbsPosN[0].step = MaxPositionN[0].value / 50.0;
119  FocusAbsPosN[0].value = 0;
120 
121  FocusRelPosN[0].min = 0;
122  FocusRelPosN[0].max = (FocusAbsPosN[0].max - FocusAbsPosN[0].min) / 2;
123  FocusRelPosN[0].step = FocusRelPosN[0].max / 100.0;
124  FocusRelPosN[0].value = 100;
125 
127 
128  // Rotator Ticks
129  IUFillNumber(&RotatorAbsPosN[0], "ROTATOR_ABSOLUTE_POSITION", "Ticks", "%.f", 0., 61802., 1., 0.);
130  IUFillNumberVector(&RotatorAbsPosNP, RotatorAbsPosN, 1, getDeviceName(), "ABS_ROTATOR_POSITION", "Goto", ROTATOR_TAB, IP_RW,
131  0, IPS_IDLE );
132  RotatorAbsPosN[0].min = 0;
133 
134  addDebugControl();
135 
136  // The device uses an Arduino which shows up as /dev/ttyACM0 on Linux
137  // An udev rule example is provided that can create a more logical name like /dev/integra_focusing_rotator0
138  serialConnection->setDefaultPort("/dev/ttyACM0");
139  // Set mandatory baud speed. The device does not work with anything else.
141 
143 
144  return true;
145 }
146 
148 {
150 
151  if (isConnected())
152  {
153  defineProperty(&MaxPositionNP);
154  // Focus
155  defineProperty(&SensorNP);
156  defineProperty(&FindHomeSP);
157 
158  // Rotator
160  defineProperty(&RotatorAbsPosNP);
161  }
162  else
163  {
164  deleteProperty(MaxPositionNP.name);
165 
166  // Focus
167  deleteProperty(SensorNP.name);
168  deleteProperty(FindHomeSP.name);
169 
170  // Rotator
172  deleteProperty(RotatorAbsPosNP.name);
173  }
174 
175  return true;
176 }
177 
178 // This is called from Serial::processHandshake
180 {
181  bool rcFirmware = getFirmware();
182  bool rcMaxPositionMotorFocus = getMaxPosition(MOTOR_FOCUS);
183  bool rcMaxPositionMotorRotator = getMaxPosition(MOTOR_ROTATOR);
184  bool rcType = getFocuserType(); // keep this after the getMaxPositions
185  if (rcFirmware && rcMaxPositionMotorFocus && rcMaxPositionMotorRotator && rcType)
186  {
187  return true;
188  }
189 
190  LOG_ERROR("Error retrieving data from Integra, please ensure Integra controller is powered, port choice is correct and baud rate is 115200.");
191  return false;
192 }
193 
195 {
196  return "Integra85";
197 }
198 
199 void Integra::cleanPrint(const char *cmd, char *cleancmd)
200 {
201  size_t len = strlen(cmd);
202  int j = 0;
203  for (size_t i = 0; i <= len; i++)
204  {
205  if (cmd[i] == 0xA)
206  {
207  cleancmd[j++] = '\\';
208  cleancmd[j++] = 'n';
209  }
210  else if (cmd[i] == 0xD)
211  {
212  cleancmd[j++] = '\\';
213  cleancmd[j++] = 'r';
214  }
215  else
216  {
217  cleancmd[j++] = cmd[i];
218  }
219  }
220 }
221 
222 // Called from our ::Handshake
223 bool Integra::getFirmware()
224 {
225  // two firmware versions (in ISO date format) : 2017-01-25 and 2017-12-20
226  // still no direct command to retrieve the version but the protocol
227  // has changed. the later version is returning the full command prefix as response prefix
228  // version 2017-01-25 : cmd RR1,0 => R188600#
229  // version 2017-12-20 : cmd RR1.0 => RR188600#
230 
231  // to identify the version we try both protocols, newest first.
232  if ( genericIntegraCommand(__FUNCTION__, "@RR1,0\r\n", "RR", nullptr))
233  {
234  LOGF_INFO("Firmware version is %s", "2017-12-20");
235  this->firmwareVersion = VERSION_20171220;
236  }
237  else if ( genericIntegraCommand(__FUNCTION__, "@RR1,0\r\n", "R", nullptr))
238  {
239  LOGF_INFO("Firmware version is %s, note: there is a firmware upgrade available.", "2017-01-25");
240  this->firmwareVersion = VERSION_20170125;
241  }
242  else
243  {
244  LOG_ERROR("Cannot determine firmware version, there may be a firmware upgrade available.");
245  return false; // cannot retrieve firmware session.
246  }
247 
248  return true;
249 }
250 
251 // Called from our ::Handshake
252 bool Integra::getFocuserType()
253 {
254  int focus_max = int(FocusAbsPosN[0].max);
255  int rotator_max = int(RotatorAbsPosN[0].max);
256  if (focus_max != wellKnownIntegra85FocusMax)
257  {
258  LOGF_ERROR("This is no Integra85 because focus max position %d != %d, trying to continue still", focus_max,
260  // return false;
261  }
262  if (rotator_max != wellKnownIntegra85RotateMax)
263  {
264  LOGF_ERROR("This is no Integra85 because rotator max position %d != %d, trying to continue still", rotator_max,
266  // return false;
267  }
268 
269  char resp[64] = "Integra85"; // TODO this is an assumption until the device can report its type
270  LOGF_INFO("Focuser Type %s", resp);
271  if (strcmp(resp, "Integra85") == 0)
272  {
273  // RotatorAbsPosN[0].max is already set by getMaxPosition
274  rotatorTicksPerDegree = RotatorAbsPosN[0].max / 360.0;
275  rotatorDegreesPerTick = 360.0 / RotatorAbsPosN[0].max;
276  }
277 
278  return true;
279 }
280 
281 bool Integra::relativeGotoMotor(MotorType type, int32_t relativePosition)
282 {
283  int motorMoveCommand;
284 
285  LOGF_DEBUG("Start relativeGotoMotor to %d ...", relativePosition);
286  if (relativePosition > 0)
287  motorMoveCommand = move_mot_out;
288  else
289  motorMoveCommand = move_mot_in;
290 
291  if (type == MOTOR_FOCUS)
292  {
293  if (relativePosition > 0)
294  {
295  if (lastFocuserPosition + relativePosition > MaxPositionN[MOTOR_FOCUS].value)
296  {
297  int newRelativePosition = (int32_t)floor(MaxPositionN[MOTOR_FOCUS].value) - lastFocuserPosition;
298  LOGF_INFO("Focus position change %d clipped to %d to stay at MAX %d",
299  relativePosition, newRelativePosition, MaxPositionN[MOTOR_FOCUS].value);
300  relativePosition = newRelativePosition;
301  }
302  }
303  else
304  {
305  if ((int32_t )lastFocuserPosition + relativePosition < 0)
306  {
307  int newRelativePosition = -lastFocuserPosition;
308  LOGF_INFO("Focus position change %d clipped to %d to stay at MIN 0",
309  relativePosition, newRelativePosition);
310  relativePosition = newRelativePosition;
311  }
312  }
313  }
314  else if (type == MOTOR_ROTATOR)
315  {
316  if (relativePosition > 0)
317  {
318  if (lastRotatorPosition + relativePosition > MaxPositionN[MOTOR_ROTATOR].value)
319  {
320  int newRelativePosition = (int32_t)floor(MaxPositionN[MOTOR_ROTATOR].value) - lastRotatorPosition;
321  LOGF_INFO("Rotator position change %d clipped to %d to stay at MAX %d",
322  relativePosition, newRelativePosition, MaxPositionN[MOTOR_ROTATOR].value);
323  relativePosition = newRelativePosition;
324  }
325  }
326  else
327  {
328  if (lastRotatorPosition + relativePosition < - MaxPositionN[MOTOR_ROTATOR].value)
329  {
330  int newRelativePosition = - (int32_t)floor(MaxPositionN[MOTOR_ROTATOR].value) - lastRotatorPosition;
331  LOGF_INFO("Rotator position change %d clipped to %d to stay at MIN %d",
332  relativePosition, newRelativePosition, - MaxPositionN[MOTOR_ROTATOR].value);
333  relativePosition = newRelativePosition;
334  }
335  }
336  }
337 
338  return integraMotorSetCommand( __FUNCTION__, motorMoveCommand, type, abs(relativePosition), nullptr);
339 }
340 
341 bool Integra::gotoMotor(MotorType type, int32_t position)
342 {
343  LOGF_DEBUG("Start gotoMotor to %d", position);
344  if (type == MOTOR_FOCUS)
345  {
346  return relativeGotoMotor(type, position - lastFocuserPosition);
347  }
348  else if (type == MOTOR_ROTATOR)
349  {
350  return relativeGotoMotor(type, position - lastRotatorPosition);
351  }
352  else
353  {
354  LOGF_ERROR("%s error: MotorType %d is unknown.", __FUNCTION__, type);
355  }
356  return false;
357 }
358 
359 bool Integra::getPosition(MotorType type)
360 {
361  char res[16] = {0};
362  if ( !integraMotorGetCommand(__FUNCTION__, get_motstep, type, res ))
363  {
364  return false;
365  }
366  int position = -1e6;
367  position = atoi(res);
368  if (position != -1e6)
369  {
370  if (type == MOTOR_FOCUS)
371  {
372  if (FocusAbsPosN[0].value != position)
373  {
374  auto position_from = (int) FocusAbsPosN[0].value;
375  int position_to = position;
376  if (haveReadFocusPositionAtLeastOnce)
377  {
378  LOGF_DEBUG("Focus position changed from %d to %d", position_from, position_to);
379  }
380  else
381  {
382  LOGF_DEBUG("Focus position is %d", position_to);
383  }
384  FocusAbsPosN[0].value = position;
385  }
386  }
387  else if (type == MOTOR_ROTATOR)
388  {
389  if (RotatorAbsPosN[0].value != position)
390  {
391  auto position_from = (int) RotatorAbsPosN[0].value;
392  int position_to = position;
393  if (haveReadRotatorPositionAtLeastOnce)
394  {
395  LOGF_DEBUG("Rotator changed angle from %.2f to %.2f, position from %d to %d",
396  rotatorTicksToDegrees(position_from), rotatorTicksToDegrees(position_to), position_from, position_to);
397  }
398  else
399  {
400  LOGF_DEBUG("Rotator angle is %.2f, position is %d",
401  rotatorTicksToDegrees(position_to), position_to);
402  }
403  RotatorAbsPosN[0].value = position;
404  }
405  }
406  else
407  {
408  LOGF_ERROR("%s error: motor type %d is unknown", __FUNCTION__, type);
409  }
410 
411  return true;
412  }
413 
414  LOGF_DEBUG("Invalid Position! %d", position);
415  return false;
416 }
417 
418 bool Integra::ISNewSwitch (const char * dev, const char * name, ISState * states, char * names[], int n)
419 {
420  if(strcmp(dev, getDeviceName()) == 0)
421  {
422  if (strcmp(name, FindHomeSP.name) == 0)
423  {
424  IUUpdateSwitch(&FindHomeSP, states, names, n);
425  int index = IUFindOnSwitchIndex(&FindHomeSP);
426  switch (index)
427  {
428  case HOMING_IDLE:
429  LOG_INFO("Homing state is IDLE");
430  FindHomeS[HOMING_IDLE].s = ISS_ON;
431  FindHomeSP.s = IPS_OK;
432  break;
433  case HOMING_START:
434  if (findHome())
435  {
436  FindHomeSP.s = IPS_BUSY;
437  FindHomeS[HOMING_START].s = ISS_ON;
439  "Homing process can take up to 2 minutes. You cannot control the unit until the process is fully complete.");
440  }
441  else
442  {
443  FindHomeSP.s = IPS_ALERT;
444  FindHomeS[HOMING_START].s = ISS_OFF;
445  LOG_ERROR("Failed to start homing process.");
446  }
447  break;
448  case HOMING_ABORT:
449  if (abortHome())
450  {
451  FindHomeSP.s = IPS_IDLE;
452  FindHomeS[HOMING_ABORT].s = ISS_ON;
453  LOG_WARN("Homing aborted");
454  }
455  else
456  {
457  FindHomeSP.s = IPS_ALERT;
458  FindHomeS[HOMING_ABORT].s = ISS_OFF;
459  LOG_ERROR("Failed to abort homing process.");
460  }
461  break;
462  default:
463  FindHomeSP.s = IPS_ALERT;
464  IDSetSwitch(&FindHomeSP, "Unknown homing index %d", index);
465  return false;
466  }
467  IDSetSwitch(&FindHomeSP, nullptr);
468  return true;
469  }
470  else if (strstr(name, "ROTATOR"))
471  {
472  if (INDI::RotatorInterface::processSwitch(dev, name, states, names, n))
473  return true;
474  }
475  }
476 
477  return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
478 }
479 
480 bool Integra::ISNewNumber (const char * dev, const char * name, double values[], char * names[], int n)
481 {
482  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
483  {
484  if (strcmp(name, RotatorAbsPosNP.name) == 0)
485  {
486  RotatorAbsPosNP.s = (gotoMotor(MOTOR_ROTATOR, static_cast<int32_t>(values[0])) ? IPS_BUSY : IPS_ALERT);
487  IDSetNumber(&RotatorAbsPosNP, nullptr);
488  if (RotatorAbsPosNP.s == IPS_BUSY)
489  LOGF_DEBUG("Rotator moving from %d to %.f ticks...", lastRotatorPosition, values[0]);
490  return true;
491  }
492  else if (strstr(name, "ROTATOR"))
493  {
494  if (INDI::RotatorInterface::processNumber(dev, name, values, names, n))
495  return true;
496  }
497  }
498 
499  return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
500 }
501 
502 IPState Integra::MoveAbsFocuser(uint32_t targetTicks)
503 {
504  targetPosition = targetTicks;
505  LOGF_DEBUG("Focuser will move absolute from %d to %d ...", lastFocuserPosition, targetTicks);
506 
507  bool rc = false;
508  rc = gotoMotor(MOTOR_FOCUS, targetPosition);
509  if (!rc)
510  return IPS_ALERT;
511 
513 
514  return IPS_BUSY;
515 }
516 
518 {
519  double newPosition = 0;
520  bool rc = false;
521  LOGF_DEBUG("Focuser will move in direction %d relative %d ticks...", dir, ticks);
522 
523  if (dir == FOCUS_INWARD)
524  newPosition = FocusAbsPosN[0].value - ticks;
525  else
526  newPosition = FocusAbsPosN[0].value + ticks;
527 
528  rc = gotoMotor(MOTOR_FOCUS, newPosition);
529  if (!rc)
530  return IPS_ALERT;
531 
532  FocusRelPosN[0].value = ticks;
534 
535  return IPS_BUSY;
536 }
537 
539 {
540  if (!isConnected())
541  {
543  return;
544  }
545 
546  bool rc = false;
547  bool savePositionsToEEPROM = false;
548 
549  // sanity check warning ...
550  if (int(FocusAbsPosN[0].max) != wellKnownIntegra85FocusMax || int(RotatorAbsPosN[0].max) != wellKnownIntegra85RotateMax)
551  {
552  LOGF_WARN("Warning: Focus motor max position %d should be %d and Rotator motor max position %d should be %d",
554  int(RotatorAbsPosN[0].max), wellKnownIntegra85RotateMax);
555  }
556 
557  // #1 If we're homing, we check if homing is complete as we cannot check for anything else
558  if (FindHomeSP.s == IPS_BUSY)
559  {
560  if (isHomingComplete())
561  {
562  FindHomeS[0].s = ISS_OFF;
563  FindHomeSP.s = IPS_OK;
564  IDSetSwitch(&FindHomeSP, nullptr);
565 
566  LOG_INFO("Homing is complete");
567  // Next read positions and save to EEPROM :
568  haveReadFocusPositionAtLeastOnce = false;
569  haveReadRotatorPositionAtLeastOnce = false;
570  }
571  else
572  {
573  LOG_DEBUG("Homing");
574  }
575 
577  return;
578  }
579 
580  // #2 Get Temperature, only read this when no motors are active, and about once per minute
582  && RotatorAbsPosNP.s != IPS_BUSY
583  && timeToReadTemperature <= 0)
584  {
585  rc = getTemperature();
586  if ( ! rc)
587  rc = getTemperature();
588  if (rc)
589  {
590  timeToReadTemperature = INTEGRA_TEMPERATURE_LOOP_SKIPS;
591  if (fabs(SensorN[SENSOR_TEMPERATURE].value - lastTemperature) > INTEGRA_TEMPERATURE_TRESHOLD_IN_C)
592  {
593  lastTemperature = SensorN[SENSOR_TEMPERATURE].value;
594  IDSetNumber(&SensorNP, nullptr);
595  }
596  }
597  }
598  else
599  {
600  timeToReadTemperature--;
601  }
602 
603  // #3 is not used on Integra85
604  // #4 is not used on Integra85
605 
606  // #5 Focus Position & Status
607  if (!haveReadFocusPositionAtLeastOnce || FocusAbsPosNP.s == IPS_BUSY || FocusRelPosNP.s == IPS_BUSY)
608  {
609  if ( ! isMotorMoving(MOTOR_FOCUS))
610  {
611  LOG_DEBUG("Focuser stopped");
614  rc = getPosition(MOTOR_FOCUS);
615  if (rc)
616  {
617  if (FocusAbsPosN[0].value != lastFocuserPosition)
618  {
619  lastFocuserPosition = FocusAbsPosN[0].value;
620  IDSetNumber(&FocusAbsPosNP, nullptr);
621  IDSetNumber(&FocusRelPosNP, nullptr);
622  if (haveReadFocusPositionAtLeastOnce)
623  {
624  LOGF_INFO("Focuser reached requested position %d", lastFocuserPosition);
625  }
626  else
627  {
628  LOGF_INFO("Focuser position is %d", lastFocuserPosition);
629  haveReadFocusPositionAtLeastOnce = true;
630  }
631  savePositionsToEEPROM = true;
632  }
633  }
634  }
635  else
636  {
637  LOG_DEBUG("Focusing");
638  }
639  }
640 
641  // #6 Rotator Position & Status
642  if (!haveReadRotatorPositionAtLeastOnce || RotatorAbsPosNP.s == IPS_BUSY)
643  {
644  if ( ! isMotorMoving(MOTOR_ROTATOR))
645  {
646  LOG_DEBUG("Rotator stopped");
647  RotatorAbsPosNP.s = IPS_OK;
649  rc = getPosition(MOTOR_ROTATOR);
650  if (rc)
651  {
652  if (RotatorAbsPosN[0].value != lastRotatorPosition)
653  {
654  lastRotatorPosition = RotatorAbsPosN[0].value;
655  GotoRotatorN[0].value = rotatorTicksToDegrees(
656  lastRotatorPosition); //range360(RotatorAbsPosN[0].value / rotatorTicksPerDegree);
657  IDSetNumber(&RotatorAbsPosNP, nullptr);
658  IDSetNumber(&GotoRotatorNP, nullptr);
659  if (haveReadRotatorPositionAtLeastOnce)
660  LOGF_INFO("Rotator reached requested angle %.2f, position %d",
661  rotatorTicksToDegrees(lastRotatorPosition), lastRotatorPosition);
662  else
663  {
664  LOGF_INFO("Rotator is at angle %.2f, position %d",
665  rotatorTicksToDegrees(lastRotatorPosition), lastRotatorPosition);
666  haveReadRotatorPositionAtLeastOnce = true;
667  }
668  savePositionsToEEPROM = true;
669  }
670  }
671  }
672  else
673  {
674  LOG_DEBUG("Rotating");
675  }
676  }
677 
678  // #7 is not used on Integra85
679 
680  if (savePositionsToEEPROM)
681  {
682  saveToEEPROM();
683  }
685 }
686 
688 {
689  return stopMotor(MOTOR_FOCUS);
690 }
691 
692 bool Integra::stopMotor(MotorType type)
693 {
694  // TODO (if focuser?) handle CR 2
695  if (integraMotorGetCommand(__FUNCTION__, stop_motor, type, nullptr))
696  {
697  if (type == MOTOR_FOCUS)
698  {
699  haveReadFocusPositionAtLeastOnce = false;
700  }
701  else
702  {
703  haveReadRotatorPositionAtLeastOnce = false;
704  }
705  return true;
706  }
707 
708  return false;
709 }
710 
711 bool Integra::isMotorMoving(MotorType type)
712 {
713  char res[16] = {0};
714  if ( ! integraGetCommand( __FUNCTION__, is_moving, res))
715  {
716  return false;
717  }
718  if (type == MOTOR_FOCUS)
719  {
720  if (res[0] == '1')
721  {
722  LOG_DEBUG("Focus motor is running");
723  return true;
724  }
725  else
726  {
727  LOG_DEBUG("Focus motor is not running");
728  return false;
729  }
730  }
731  else
732  {
733  // bug, both motors return 1 at res[0] when running
734  // return (res[0] == '2');
735  if (res[0] == '1')
736  {
737  LOG_DEBUG("Rotator motor is running");
738  return true;
739  }
740  else
741  {
742  LOG_DEBUG("Rotator motor is not running");
743  return false;
744  }
745  }
746 }
747 
748 // Called from our ::Handshake
749 bool Integra::getMaxPosition(MotorType type)
750 {
751  char res[16] = {0};
752  if ( ! integraMotorGetCommand(__FUNCTION__, get_motrange, type, res))
753  {
754  return false;
755  }
756  int position = atoi(res);
757  if (MaxPositionN[type].value == position)
758  {
759  LOGF_INFO("%s motor max position is %d", (type == MOTOR_FOCUS) ? "Focuser" : "Rotator", position);
760  }
761  else
762  {
763  LOGF_WARN("Updated %s motor max position from %d to %d",
764  (type == MOTOR_FOCUS) ? "Focuser" : "Rotator", MaxPositionN[type].value, position);
765  MaxPositionN[type].value = position;
766  if (type == MOTOR_FOCUS)
767  {
768  FocusAbsPosN[0].max = MaxPositionN[type].value;
769  }
770  else if (type == MOTOR_ROTATOR)
771  {
772  RotatorAbsPosN[0].max = MaxPositionN[type].value;
773  }
774  else
775  {
776  LOG_ERROR("Unknown Motor type");
777  }
778  }
779  return position > 0; // cannot consider a max position == 0 as a valid max.
780 }
781 
782 bool Integra::saveToEEPROM()
783 {
784  return integraGetCommand(__FUNCTION__, EEPROMwrite, nullptr);
785 }
786 
787 bool Integra::getTemperature()
788 {
789  char res[16] = {0};
790  if (integraGetCommand(__FUNCTION__, get_temperature, res ) )
791  {
792  SensorN[SENSOR_TEMPERATURE].value = strtod(res, nullptr);
793  return true;
794  }
795  return false;
796 }
797 
798 bool Integra::findHome()
799 {
800  return integraMotorGetCommand(__FUNCTION__, calibrate, MOTOR_FOCUS, nullptr);
801 }
802 
803 bool Integra::abortHome()
804 {
805  return integraMotorGetCommand(__FUNCTION__, calibrate_interrupt, MOTOR_FOCUS, nullptr);
806 }
807 
808 bool Integra::isHomingComplete()
809 {
810  char res[16] = {0};
811  if (integraMotorGetCommand(__FUNCTION__, calibration_state, MOTOR_FOCUS, res))
812  {
813  return (res[0] == '1');
814  }
815  return false;
816 }
817 
819 {
820  Focuser::saveConfigItems(fp);
821  return true;
822 }
823 
824 // Integra position 0..61802 ticks , angle 0..360 deg, position 0 corresponds to 180 deg
825 // We need to map the Integra frame to that of the IndiRotatorInterface.
826 // INDI rotatorInterface: Only absolute position Rotators are supported.
827 // Angle is ranged from 0 to 360 increasing clockwise when looking at the back of the camera.
829 {
830  uint32_t p1 = lastRotatorPosition;
831  uint32_t p2 = rotatorDegreesToTicks(angle);
832 
833  LOGF_INFO("MoveRotator from %.2f to %.2f degrees, from position %d to %d ...",
834  rotatorTicksToDegrees(lastRotatorPosition), angle, p1, p2);
835  bool rc = relativeGotoMotor(MOTOR_ROTATOR, p2 - p1);
836  if (rc)
837  {
838  RotatorAbsPosNP.s = IPS_BUSY;
839  IDSetNumber(&RotatorAbsPosNP, nullptr);
840  return IPS_BUSY;
841  }
842 
843  return IPS_ALERT;
844 }
845 
847 {
848  bool rc = stopMotor(MOTOR_ROTATOR);
849  if (rc && RotatorAbsPosNP.s != IPS_OK)
850  {
851  RotatorAbsPosNP.s = IPS_OK;
852  IDSetNumber(&RotatorAbsPosNP, nullptr);
853  }
854 
855  return rc;
856 }
857 
858 uint32_t Integra::rotatorDegreesToTicks(double angle)
859 {
860  uint32_t position = 61802 / 2;
861  if (angle >= 0.0 && angle <= 180.0)
862  {
863  position = (uint32_t) lround(61802.0 - (180.0 - angle) / rotatorDegreesPerTick);
864  }
865  else if (angle > 180 && angle <= 360)
866  {
867  position = (uint32_t) lround(61802.0 - (540.0 - angle) / rotatorDegreesPerTick);
868  }
869  else
870  {
871  LOGF_ERROR("%s error: %.2f is out of range", __FUNCTION__, angle);
872  }
873  return position;
874 }
875 
876 double Integra::rotatorTicksToDegrees(uint32_t ticks)
877 {
878  double degrees = range360(180.0 + ticks * rotatorDegreesPerTick + INTEGRA_ROUNDING_FUDGE);
879  return degrees;
880 }
881 
882 bool Integra::SyncRotator(double angle)
883 {
884  uint32_t position = rotatorDegreesToTicks(angle);
885  if ( integraMotorSetCommand(__FUNCTION__, set_motstep, MOTOR_ROTATOR, position, nullptr ))
886  {
887  haveReadRotatorPositionAtLeastOnce = false;
888  return true;
889  }
890  return false;
891 }
892 
894 {
895  return integraMotorGetCommand(__FUNCTION__, invert_dir, MOTOR_ROTATOR, nullptr);
896 }
897 
898 bool Integra::integraGetCommand( const char *name, int commandIdx, char *returnValueString )
899 {
900  char cmd[16] = {0};
901  snprintf(cmd, 16, "%s", IntegraProtocol[commandIdx].cmd);
902  return genericIntegraCommand(name, cmd, IntegraProtocol[commandIdx].ret[this->firmwareVersion], returnValueString);
903 }
904 
905 bool Integra::integraMotorGetCommand( const char *name, int commandIdx, MotorType motor, char *returnValueString )
906 {
907  char cmd[16] = {0};
908  snprintf(cmd, 16, IntegraProtocol[commandIdx].cmd, motor + 1);
909  return genericIntegraCommand(name, cmd, IntegraProtocol[commandIdx].ret[this->firmwareVersion], returnValueString);
910 }
911 
912 bool Integra::integraMotorSetCommand(const char *name, int commandIdx, MotorType motor, int value,
913  char *returnValueString )
914 {
915  char cmd[16] = {0};
916  snprintf(cmd, 16, IntegraProtocol[commandIdx].cmd, motor + 1, value);
917  return genericIntegraCommand(name, cmd, IntegraProtocol[commandIdx].ret[this->firmwareVersion], returnValueString);
918 }
919 
920 bool Integra::genericIntegraCommand(const char *name, const char *cmd, const char *expectStart, char *returnValueString)
921 {
922  char cmdnocrlf[16] = {0};
923  int nbytes_written = 0, nbytes_read = 0, rc = -1;
924  char res[16] = {0};
925  char *correctRes = nullptr;
926  char errstr[MAXRBUF];
927 
928  cleanPrint(cmd, cmdnocrlf);
929  LOGF_DEBUG("CMD %s (%s)", name, cmdnocrlf);
930 
931  tcflush(PortFD, TCIOFLUSH);
932  if ( (rc = tty_write(PortFD, cmd, (int)strlen(cmd), &nbytes_written)) != TTY_OK)
933  {
934  tty_error_msg(rc, errstr, MAXRBUF);
935  LOGF_ERROR("%s: %s.", name, errstr);
936  return false;
937  }
938 
939  if ( (rc = tty_read_section(PortFD, res, '#', INTEGRA_TIMEOUT_IN_S, &nbytes_read)) != TTY_OK)
940  {
941  tty_error_msg(rc, errstr, MAXRBUF);
942  LOGF_ERROR("%s error: %s.", name, errstr);
943  return false;
944  }
945 
946  LOGF_DEBUG("RES %s (%s)", name, res);
947 
948  // check begin of result string
949  if (expectStart != nullptr)
950  {
951  correctRes = strstr(res, expectStart); // the hw sometimes returns /r or /n at the beginning ot the response
952  if (correctRes == nullptr)
953  {
954  LOGF_ERROR("%s error: invalid response (%s)", name, res);
955  return false;
956  }
957  }
958  // check end of result string
959  if (res[nbytes_read - 1] != '#')
960  {
961  LOGF_ERROR("%s error: invalid response 2 (%s)", name, res);
962  return false;
963  }
964  res[nbytes_read - 1] = '\0'; // wipe the #
965 
966  if (returnValueString != nullptr && expectStart != nullptr)
967  {
968  size_t expectStrlen = strlen(expectStart);
969  strcpy(returnValueString, correctRes + expectStrlen);
970  }
971 
972  return true;
973 }
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
void setDefaultPort(const char *port)
setDefaultPort Set default port. Call this function in initProperties() of your driver if you want to...
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
void setDriverInterface(uint16_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
uint16_t getDriverInterface() const
void addDebugControl()
Add Debug control to the driver.
INumberVectorProperty FocusAbsPosNP
INumberVectorProperty FocusRelPosNP
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: indifocuser.cpp:42
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Connection::Serial * serialConnection
Definition: indifocuser.h:116
void setSupportedConnections(const uint8_t &value)
setConnection Set Focuser connection mode. Child class should call this in the constructor before Foc...
bool processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process Rotator switch properties.
INumberVectorProperty GotoRotatorNP
void initProperties(const char *groupName)
Initilize Rotator properties. It is recommended to call this function within initProperties() of your...
bool updateProperties()
updateProperties Define or Delete Rotator properties based on the connection status of the base devic...
void SetCapability(uint32_t cap)
SetRotatorCapability sets the Rotator capabilities. All capabilities must be initialized.
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process Rotator number properties.
Integra()
Definition: integra.cpp:80
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: integra.cpp:89
const char * getDefaultName() override
Definition: integra.cpp:194
virtual bool AbortRotator() override
AbortRotator Abort all motion.
Definition: integra.cpp:846
virtual bool ReverseRotator(bool enabled) override
ReverseRotator Reverse the direction of the rotator. CW is usually the normal direction,...
Definition: integra.cpp:893
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition: integra.cpp:480
@ HOMING_START
Definition: integra.h:40
@ HOMING_ABORT
Definition: integra.h:41
@ HOMING_COUNT
Definition: integra.h:42
@ HOMING_IDLE
Definition: integra.h:39
@ VERSION_20171220
Definition: integra.h:31
@ VERSION_20170125
Definition: integra.h:31
virtual IPState MoveRotator(double angle) override
MoveRotator Go to specific angle.
Definition: integra.cpp:828
const int wellKnownIntegra85RotateMax
Definition: integra.h:35
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition: integra.cpp:418
virtual bool SyncRotator(double angle) override
SyncRotator Set current angle as the supplied angle without moving the rotator.
Definition: integra.cpp:882
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
Definition: integra.cpp:538
virtual IPState MoveAbsFocuser(uint32_t targetTicks) override
MoveFocuser the focuser to an absolute position.
Definition: integra.cpp:502
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the Device Port and Focuser Presets in the configuration file
Definition: integra.cpp:818
virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveFocuser the focuser to an relative position.
Definition: integra.cpp:517
virtual bool Handshake() override
perform handshake with device to check communication
Definition: integra.cpp:179
@ MOTOR_FOCUS
Definition: integra.h:29
@ MOTOR_ROTATOR
Definition: integra.h:29
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition: integra.cpp:147
virtual bool AbortFocuser() override
AbortFocuser all focus motion.
Definition: integra.cpp:687
const int wellKnownIntegra85FocusMax
Definition: integra.h:34
Provides interface to implement Rotator functionality.
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
double max(void)
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
@ IP_RW
Definition: indiapi.h:186
@ IP_RO
Definition: indiapi.h:184
IPState
Property state.
Definition: indiapi.h:160
@ IPS_BUSY
Definition: indiapi.h:163
@ IPS_ALERT
Definition: indiapi.h:164
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ ISR_1OFMANY
Definition: indiapi.h:173
int tty_read_section(int fd, char *buf, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition: indicom.c:566
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition: indicom.c:424
double range360(double r)
range360 Limits an angle to be between 0-360 degrees.
Definition: indicom.c:1245
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition: indicom.c:1167
Implementations for common driver routines.
@ TTY_OK
Definition: indicom.h:150
void IUFillNumberVector(INumberVectorProperty *nvp, INumber *np, int nnp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a number vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:272
int IUFindOnSwitchIndex(const ISwitchVectorProperty *svp)
Returns the index of first ON switch it finds in the vector switch property.
Definition: indidevapi.c:128
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
Definition: indidevapi.c:158
void IUFillNumber(INumber *np, const char *name, const char *label, const char *format, double min, double max, double step, double value)
Assign attributes for a number property. The number's auxiliary elements will be set to NULL.
Definition: indidevapi.c:180
void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:235
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
Definition: indidriver.c:1308
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOG_DEBUG(txt)
Definition: indilogger.h:75
#define DEBUG(priority, msg)
Macro to print log messages. Example of usage of the Logger: DEBUG(DBG_DEBUG, "hello " << "world");.
Definition: indilogger.h:56
#define LOGF_WARN(fmt,...)
Definition: indilogger.h:81
#define LOG_WARN(txt)
Definition: indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition: indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition: indilogger.h:80
#define LOG_INFO(txt)
Definition: indilogger.h:74
#define MAXRBUF
Definition: indiserver.cpp:102
struct COMMANDDESC COMMANDDESC
#define ROTATOR_TAB
Definition: integra.cpp:35
#define INTEGRA_TEMPERATURE_TRESHOLD_IN_C
Definition: integra.cpp:32
#define INTEGRA_TEMPERATURE_LOOP_SKIPS
Definition: integra.cpp:31
std::unique_ptr< Integra > integra(new Integra())
#define INTEGRA_TIMEOUT_IN_S
Definition: integra.cpp:30
@ EEPROMwrite
Definition: integra.cpp:77
@ get_temperature
Definition: integra.cpp:69
@ get_motstep
Definition: integra.cpp:71
@ set_motstep
Definition: integra.cpp:70
@ move_mot_in
Definition: integra.cpp:72
@ stop_motor
Definition: integra.cpp:65
@ calibrate_interrupt
Definition: integra.cpp:67
@ is_moving
Definition: integra.cpp:75
@ move_mot_out
Definition: integra.cpp:73
@ get_motrange
Definition: integra.cpp:74
@ calibration_state
Definition: integra.cpp:68
@ invert_dir
Definition: integra.cpp:76
@ calibrate
Definition: integra.cpp:66
#define INTEGRA_ROUNDING_FUDGE
Definition: integra.cpp:33
#define SETTINGS_TAB
Definition: integra.cpp:36
__le16 type
Definition: pwc-ioctl.h:0
__u8 cmd[4]
Definition: pwc-ioctl.h:2
const char cmd[12]
Definition: integra.cpp:42
char ret[2][3]
Definition: integra.cpp:43
char name[MAXINDINAME]
Definition: indiapi.h:323
char name[MAXINDINAME]
Definition: indiapi.h:371