Instrument Neutral Distributed Interface INDI  2.0.2
usbfocusv3.cpp
Go to the documentation of this file.
1 /*
2  USB Focus V3
3  Copyright (C) 2016 G. Schmidt
4  Copyright (C) 2018-2023 Jarno Paananen
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Lesser General Public
8  License as published by the Free Software Foundation; either
9  version 2.1 of the License, or (at your option) any later version.
10 
11  This library is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  Lesser General Public License for more details.
15 
16  You should have received a copy of the GNU Lesser General Public
17  License along with this library; if not, write to the Free Software
18  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 
20 */
21 
22 #include "usbfocusv3.h"
23 
24 #include "indicom.h"
25 
26 #include <cmath>
27 #include <cstring>
28 #include <memory>
29 
30 #include <termios.h>
31 #include <unistd.h>
32 
33 #define USBFOCUSV3_TIMEOUT 5
34 
35 /***************************** Class USBFocusV3 *******************************/
36 
37 static std::unique_ptr<USBFocusV3> usbFocusV3(new USBFocusV3());
38 
40 {
41  setVersion(1, 1);
42  // Can move in Absolute & Relative motions, can AbortFocuser motion, and has variable speed.
48 }
49 
51 {
53 
54  /*** init controller parameters ***/
55 
56  direction = 0;
57  stepmode = 1;
58  speed = 3;
59  stepsdeg = 20;
60  tcomp_thr = 5;
61  firmware = 0;
62  maxpos = 65535;
63 
64  /*** init driver parameters ***/
65 
66  FocusSpeedN[0].min = 1;
67  FocusSpeedN[0].max = 3;
68  FocusSpeedN[0].value = 2;
69 
70  // Step Mode
71  StepModeSP[UFOPHSTEPS].fill("HALF", "Half Step", ISS_ON);
72  StepModeSP[UFOPFSTEPS].fill("FULL", "Full Step", ISS_OFF);
73  StepModeSP.fill(getDeviceName(), "STEP_MODE", "Step Mode", OPTIONS_TAB, IP_RW,
74  ISR_1OFMANY, 0, IPS_IDLE);
75 
76  // Direction
77  RotDirSP[UFOPSDIR].fill("STANDARD", "Standard rotation", ISS_ON);
78  RotDirSP[UFOPRDIR].fill("REVERSE", "Reverse rotation", ISS_OFF);
79  RotDirSP.fill(getDeviceName(), "ROTATION_MODE", "Rotation Mode", OPTIONS_TAB, IP_RW,
80  ISR_1OFMANY, 0, IPS_IDLE);
81 
82  // Focuser temperature
83  TemperatureNP[0].fill("TEMPERATURE", "Celsius", "%6.2f", -50., 70., 0., 0.);
84  TemperatureNP.fill(getDeviceName(), "FOCUS_TEMPERATURE", "Temperature",
86 
87  // Maximum Position
88  MaxPositionNP[0].fill("MAXPOSITION", "Maximum position", "%5.0f", 1., 65535., 0., 65535.);
89  MaxPositionNP.fill(getDeviceName(), "FOCUS_MAXPOSITION", "Max. Position",
91 
92  // Temperature Settings
93  TemperatureSettingNP[0].fill("COEFFICIENT", "Coefficient", "%3.0f", 0., 999., 1., 15.);
94  TemperatureSettingNP[1].fill("THRESHOLD", "Threshold", "%3.0f", 0., 999., 1., 10.);
95  TemperatureSettingNP.fill(getDeviceName(), "TEMPERATURE_SETTINGS",
96  "Temp. Settings", OPTIONS_TAB, IP_RW, 0, IPS_IDLE);
97 
98  // Temperature Compensation Sign
99  TempCompSignSP[UFOPNSIGN].fill("NEGATIVE", "Negative", ISS_OFF);
100  TempCompSignSP[UFOPPSIGN].fill("POSITIVE", "Positive", ISS_ON);
101  TempCompSignSP.fill(getDeviceName(), "TCOMP_SIGN", "TComp. Sign", OPTIONS_TAB,
102  IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
103 
104  // Compensate for temperature
105  TemperatureCompensateSP[0].fill("ENABLE", "Enable", ISS_OFF);
106  TemperatureCompensateSP[1].fill("DISABLE", "Disable", ISS_ON);
107  TemperatureCompensateSP.fill(getDeviceName(), "TEMP_COMPENSATION",
108  "Temp. Comp.", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
109 
110  FocusBacklashN[0].min = -65535.;
111  FocusBacklashN[0].max = 65535.;
112  FocusBacklashN[0].step = 1000.;
113  FocusBacklashN[0].value = 0.;
114 
115  // Reset
116  ResetSP[0].fill("RESET", "Reset", ISS_OFF);
117  ResetSP.fill(getDeviceName(), "RESET", "Reset", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0,
118  IPS_IDLE);
119 
120  // Firmware version
121  FWversionNP[0].fill("FIRMWARE", "Firmware Version", "%5.0f", 0., 65535., 1., 0.);
122  FWversionNP.fill(getDeviceName(), "FW_VERSION", "Firmware", OPTIONS_TAB, IP_RO, 0,
123  IPS_IDLE);
124 
125  /* Relative and absolute movement */
126  FocusRelPosN[0].min = 0.;
127  FocusRelPosN[0].max = float(maxpos);
128  FocusRelPosN[0].value = 0;
129  FocusRelPosN[0].step = 1;
130 
131  FocusAbsPosN[0].min = 0.;
132  FocusAbsPosN[0].max = float(maxpos);
133  FocusAbsPosN[0].value = 0;
134  FocusAbsPosN[0].step = 1;
135 
136  addDebugControl();
137 
139 
140  return true;
141 }
142 
144 {
146 
147  if (isConnected())
148  {
149  defineProperty(TemperatureNP);
150  defineProperty(MaxPositionNP);
151  defineProperty(StepModeSP);
152  defineProperty(RotDirSP);
153  defineProperty(MaxPositionNP);
154  defineProperty(TemperatureSettingNP);
155  defineProperty(TempCompSignSP);
156  defineProperty(TemperatureCompensateSP);
157  defineProperty(ResetSP);
158  defineProperty(FWversionNP);
159 
160  GetFocusParams();
161 
162  loadConfig(true);
163 
164  LOG_INFO("USBFocusV3 parameters updated, focuser ready for use.");
165  }
166  else
167  {
168  deleteProperty(TemperatureNP);
169  deleteProperty(MaxPositionNP);
170  deleteProperty(StepModeSP);
171  deleteProperty(RotDirSP);
172  deleteProperty(TemperatureSettingNP);
173  deleteProperty(TempCompSignSP);
174  deleteProperty(TemperatureCompensateSP);
175  deleteProperty(ResetSP);
176  deleteProperty(FWversionNP);
177  }
178 
179  return true;
180 }
181 
183 {
184  int tries = 2;
185 
186  do
187  {
188  if (Ack())
189  {
190  LOG_INFO("USBFocusV3 is online. Getting focus parameters...");
191  return true;
192  }
193  LOG_INFO("Error retrieving data from USBFocusV3, trying resync...");
194  }
195  while (--tries > 0 && Resync());
196 
197  LOG_INFO("Error retrieving data from USBFocusV3, please ensure controller "
198  "is powered and the port is correct.");
199  return false;
200 }
201 
203 {
204  return "USBFocusV3";
205 }
206 
207 bool USBFocusV3::Resync()
208 {
209  char cmd[] = " "; // Illegal command to trigger error response
210  int nbytes_written = 0, nbytes_read = 0, rc = -1;
211  char errstr[MAXRBUF];
212  char resp[UFORESLEN] = {};
213 
214  // Send up to 5 space characters and wait for error
215  // response ("ER=1") after which the communication
216  // is back in sync
217  tcflush(PortFD, TCIOFLUSH);
218 
219  for (int resync = 0; resync < UFOCMDLEN; resync++)
220  {
221  LOGF_INFO("Retry %d...", resync + 1);
222 
223  if ((rc = tty_write(PortFD, cmd, 1, &nbytes_written)) != TTY_OK)
224  {
225  tty_error_msg(rc, errstr, MAXRBUF);
226  LOGF_ERROR("Error writing resync: %s.", errstr);
227  return false;
228  }
229 
230  rc = tty_nread_section(PortFD, resp, UFORESLEN, '\r', 3, &nbytes_read);
231  if (rc == TTY_OK && nbytes_read > 0)
232  {
233  // We got a response
234  return true;
235  }
236  // We didn't get response yet, retry
237  }
238  LOG_ERROR("No valid resync response.");
239  return false;
240 }
241 
242 bool USBFocusV3::sendCommand(const char *cmd, char *resp)
243 {
244  pthread_mutex_lock(&cmdlock);
245 
246  int nbytes_written = 0, nbytes_read = 0, rc = -1;
247  char errstr[MAXRBUF];
248  LOGF_DEBUG("CMD: %s.", cmd);
249 
250  tcflush(PortFD, TCIOFLUSH);
251  if ((rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
252  {
253  tty_error_msg(rc, errstr, MAXRBUF);
254  LOGF_WARN("Error writing command %s: %s.", cmd, errstr);
255  pthread_mutex_unlock(&cmdlock);
256  return false;
257  }
258 
259  if (resp)
260  {
261  if ((rc = tty_nread_section(PortFD, resp, UFORESLEN, '\r', USBFOCUSV3_TIMEOUT, &nbytes_read)) != TTY_OK)
262  {
263  tty_error_msg(rc, errstr, MAXRBUF);
264  LOGF_WARN("Error reading response for command %s: %s.", cmd, errstr);
265  pthread_mutex_unlock(&cmdlock);
266  return false;
267  }
268 
269  // If we are moving, check for ACK * response first
270  if (moving && nbytes_read > 0 && resp[0] == UFORSACK)
271  {
272  moving = false;
273  if (nbytes_read > 1)
274  {
275  // Move rest of the response down one byte
276  nbytes_read--;
277  memmove(&resp[0], &resp[1], nbytes_read);
278  }
279  }
280 
281  if (nbytes_read < 2)
282  {
283  LOGF_WARN("Invalid response for command %s: %s.", cmd, resp);
284  pthread_mutex_unlock(&cmdlock);
285  return false;
286  }
287  // Be sure we receive the \n\r to not truncate the response
288  int rc = sscanf(resp, "%s\n\r", resp);
289  if (rc <= 0)
290  {
291  LOGF_WARN("Invalid response for command %s: missing cr+lf", cmd);
292  pthread_mutex_unlock(&cmdlock);
293  return false;
294  }
295  LOGF_DEBUG("RES: %s.", resp);
296  }
297  pthread_mutex_unlock(&cmdlock);
298  return true;
299 }
300 
301 // Special version to work around command FTAXXA, which replies without \n\r
302 bool USBFocusV3::sendCommandSpecial(const char *cmd, char *resp)
303 {
304  pthread_mutex_lock(&cmdlock);
305 
306  int nbytes_written = 0, nbytes_read = 0, rc = -1;
307  char errstr[MAXRBUF];
308  LOGF_DEBUG("CMD: %s.", cmd);
309 
310  tcflush(PortFD, TCIOFLUSH);
311  if ((rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
312  {
313  tty_error_msg(rc, errstr, MAXRBUF);
314  LOGF_WARN("Error writing command %s: %s.", cmd, errstr);
315  pthread_mutex_unlock(&cmdlock);
316  return false;
317  }
318 
319  if (resp)
320  {
321  // We assume answer A=x where x is 0 or 1
322  if ((rc = tty_read(PortFD, resp, 3, USBFOCUSV3_TIMEOUT, &nbytes_read)) != TTY_OK)
323  {
324  tty_error_msg(rc, errstr, MAXRBUF);
325  LOGF_WARN("Error reading response for command %s: %s.", cmd, errstr);
326  pthread_mutex_unlock(&cmdlock);
327  return false;
328  }
329  resp[nbytes_read] = '\0';
330  LOGF_DEBUG("RES: %s.", resp);
331  }
332  pthread_mutex_unlock(&cmdlock);
333  return true;
334 }
335 
336 bool USBFocusV3::Ack()
337 {
338  char resp[UFORESLEN] = {};
339  tcflush(PortFD, TCIOFLUSH);
340 
341  if (!sendCommand(UFOCDEVID, resp))
342  return false;
343 
344  if (strncmp(resp, UFOID, UFORESLEN) != 0)
345  {
346  LOGF_ERROR("USBFocusV3 not properly identified! Answer was: %s.", resp);
347  return false;
348  }
349  return true;
350 }
351 
353 {
354  char resp[UFORESLEN] = {};
355  if (!sendCommand(UFOCREADPARAM, resp))
356  return false;
357 
358  sscanf(resp, "C=%u-%u-%u-%u-%u-%u-%u", &direction, &stepmode, &speed, &stepsdeg, &tcomp_thr, &firmware, &maxpos);
359  return true;
360 }
361 
362 bool USBFocusV3::updateStepMode()
363 {
364  StepModeSP.reset();
365 
366  if (stepmode == UFOPHSTEPS)
367  StepModeSP[UFOPHSTEPS].setState(ISS_ON);
368  else if (stepmode == UFOPFSTEPS)
369  StepModeSP[UFOPFSTEPS].setState(ISS_ON);
370  else
371  {
372  LOGF_ERROR("Unknown error: focuser step value (%d)", stepmode);
373  return false;
374  }
375  return true;
376 }
377 
378 bool USBFocusV3::updateRotDir()
379 {
380  RotDirSP.reset();
381 
382  if (direction == UFOPSDIR)
383  RotDirSP[UFOPSDIR].setState(ISS_ON);
384  else if (direction == UFOPRDIR)
385  RotDirSP[UFOPRDIR].setState(ISS_ON);
386  else
387  {
388  LOGF_ERROR("Unknown error: rotation direction (%d)", direction);
389  return false;
390  }
391 
392  return true;
393 }
394 
395 bool USBFocusV3::updateTemperature()
396 {
397  char resp[UFORESLEN] = {};
398  int j = 0;
399 
400  // retry up to 5 time to fix data desyncronization.
401  // see: https://bitbucket.org/usb-focus/ascom-driver/src/f0ec7d0faee605f9a3d9e2c4d599f9d537211201/USB_Focus/Driver.cs?at=master&fileviewer=file-view-default#Driver.cs-898
402 
403  while (j <= 5)
404  {
405  if (sendCommand(UFOCREADTEMP, resp))
406  {
407 
408  float temp;
409  int rc = sscanf(resp, "T=%f", &temp);
410 
411  if (rc > 0)
412  {
413  TemperatureNP[0].setValue(temp);
414  break;
415  }
416  else
417  {
418  LOGF_DEBUG("Unknown error: focuser temperature value (%s)", resp);
419  }
420  }
421  j++;
422  }
423  if (j > 5)
424  {
425  LOGF_ERROR("Unknown error: focuser temperature value (%s)", resp);
426  return false;
427  }
428 
429  return true;
430 }
431 
432 bool USBFocusV3::updateFWversion()
433 {
434  FWversionNP[0].setValue(firmware);
435  return true;
436 }
437 
438 bool USBFocusV3::updatePosition()
439 {
440  char resp[UFORESLEN] = {};
441  int j = 0;
442 
443  // retry up to 5 time to fix data desyncronization.
444  // see: https://bitbucket.org/usb-focus/ascom-driver/src/f0ec7d0faee605f9a3d9e2c4d599f9d537211201/USB_Focus/Driver.cs?at=master&fileviewer=file-view-default#Driver.cs-746
445 
446  while (j <= 5)
447  {
448  if (sendCommand(UFOCREADPOS, resp))
449  {
450 
451  int pos = -1;
452  int rc = sscanf(resp, "P=%u", &pos);
453 
454  if (rc > 0)
455  {
456  FocusAbsPosN[0].value = pos;
457  break;
458  }
459  else
460  {
461  LOGF_DEBUG("Unknown error: focuser position value (%s)", resp);
462  }
463  }
464  j++;
465  }
466  if (j > 5)
467  {
468  LOGF_ERROR("Unknown error: focuser position value (%s)", resp);
469  return false;
470  }
471 
472  return true;
473 }
474 
475 bool USBFocusV3::updateMaxPos()
476 {
477  MaxPositionNP[0].setValue(maxpos);
478  FocusAbsPosN[0].max = maxpos;
479  return true;
480 }
481 
482 bool USBFocusV3::updateTempCompSettings()
483 {
484  TemperatureSettingNP[0].setValue(stepsdeg);
485  TemperatureSettingNP[1].setValue(tcomp_thr);
486  return true;
487 }
488 
489 bool USBFocusV3::updateTempCompSign()
490 {
491  char resp[UFORESLEN] = {};
492  // This command seems to have a bug in firmware 1505 that it
493  // doesn't send \n\r in reply like all others except movement
494  // commands so use a special version for it
495  if (!sendCommandSpecial(UFOCGETSIGN, resp))
496  return false;
497 
498  unsigned int sign;
499  int rc = sscanf(resp, "A=%u", &sign);
500 
501  if (rc > 0)
502  {
503  TempCompSignSP.reset();
504 
505  if (sign == UFOPNSIGN)
506  TempCompSignSP[UFOPNSIGN].setState(ISS_ON);
507  else if (sign == UFOPPSIGN)
508  TempCompSignSP[UFOPPSIGN].setState(ISS_ON);
509  else
510  {
511  LOGF_ERROR("Unknown error: temp. comp. sign (%d)", sign);
512  return false;
513  }
514  }
515  else
516  {
517  LOGF_ERROR("Unknown error: temp. comp. sign value (%s)", resp);
518  return false;
519  }
520 
521  return true;
522 }
523 
524 bool USBFocusV3::updateSpeed()
525 {
526  int drvspeed;
527 
528  switch (speed)
529  {
530  case UFOPSPDAV:
531  drvspeed = 3;
532  break;
533  case UFOPSPDSL:
534  drvspeed = 2;
535  break;
536  case UFOPSPDUS:
537  drvspeed = 1;
538  break;
539  default:
540  drvspeed = 0;
541  break;
542  }
543 
544  if (drvspeed != 0)
545  {
546  currentSpeed = drvspeed;
547  FocusSpeedN[0].value = drvspeed;
548  }
549  else
550  {
551  LOGF_ERROR("Unknown error: focuser speed value (%d)", speed);
552  return false;
553  }
554 
555  return true;
556 }
557 
558 bool USBFocusV3::setAutoTempCompThreshold(unsigned int thr)
559 {
560  char cmd[UFOCMDLEN + 1];
561  snprintf(cmd, UFOCMDLEN + 1, UFOCSETTCTHR, thr);
562 
563  char resp[UFORESLEN] = {};
564  if (!sendCommand(cmd, resp))
565  return false;
566 
567  if (strncmp(resp, UFORSDONE, strlen(UFORSDONE)) == 0)
568  {
569  tcomp_thr = thr;
570  return true;
571  }
572 
573  LOG_ERROR("setAutoTempCompThreshold error: did not receive DONE.");
574 
575  return false;
576 }
577 
578 bool USBFocusV3::setTemperatureCoefficient(unsigned int coefficient)
579 {
580  char cmd[UFOCMDLEN + 1];
581  snprintf(cmd, UFOCMDLEN + 1, UFOCSETSTDEG, coefficient);
582 
583  char resp[UFORESLEN] = {};
584  if (!sendCommand(cmd, resp))
585  return false;
586 
587  if (strncmp(resp, UFORSDONE, strlen(UFORSDONE)) == 0)
588  {
589  stepsdeg = coefficient;
590  return true;
591  }
592 
593  LOG_ERROR("setTemperatureCoefficient error: did not receive DONE.");
594 
595  return false;
596 }
597 
598 bool USBFocusV3::reset()
599 {
600  char resp[UFORESLEN] = {};
601  if (!sendCommand(UFOCRESET, resp))
602  return false;
603 
604  GetFocusParams();
605 
606  return true;
607 }
608 
609 bool USBFocusV3::MoveFocuserUF(FocusDirection dir, unsigned int rticks)
610 {
611  char cmd[UFOCMDLEN + 1];
612 
613  unsigned int ticks;
614 
615  if ((dir == FOCUS_INWARD) && (rticks > FocusAbsPosN[0].value))
616  {
617  ticks = FocusAbsPosN[0].value;
618  LOGF_WARN("Requested %u ticks but inward movement has been limited to %u ticks", rticks, ticks);
619  }
620  else if ((dir == FOCUS_OUTWARD) && ((FocusAbsPosN[0].value + rticks) > MaxPositionNP[0].getValue()))
621  {
622  ticks = MaxPositionNP[0].getValue() - FocusAbsPosN[0].value;
623  LOGF_WARN("Requested %u ticks but outward movement has been limited to %u ticks", rticks, ticks);
624  }
625  else
626  {
627  ticks = rticks;
628  }
629 
630  if (dir == FOCUS_INWARD)
631  {
632  if (backlashMove == false && backlashIn == true && backlashSteps != 0)
633  {
634  ticks += backlashSteps;
635  backlashTargetPos = targetPos - backlashSteps;
636  backlashMove = true;
637  }
638  snprintf(cmd, UFOCMDLEN + 1, UFOCMOVEIN, ticks);
639  }
640  else
641  {
642  if (backlashMove == false && backlashIn == false && backlashSteps != 0)
643  {
644  ticks += backlashSteps;
645  backlashTargetPos = targetPos + backlashSteps;
646  backlashMove = true;
647  }
648  snprintf(cmd, UFOCMDLEN + 1, UFOCMOVEOUT, ticks);
649  }
650  moving = true;
651  return sendCommand(cmd, nullptr);
652 }
653 
654 bool USBFocusV3::setStepMode(FocusStepMode mode)
655 {
656  char resp[UFORESLEN] = {};
657  char cmd[UFOCMDLEN + 1];
658 
659  if (mode == FOCUS_HALF_STEP)
660  snprintf(cmd, UFOCMDLEN + 1, "%s", UFOCSETHSTEPS);
661  else
662  snprintf(cmd, UFOCMDLEN + 1, "%s", UFOCSETFSTEPS);
663 
664  if (!sendCommand(cmd, resp))
665  return false;
666 
667  stepmode = mode;
668  return true;
669 }
670 
671 bool USBFocusV3::setRotDir(unsigned int dir)
672 {
673  char resp[UFORESLEN] = {};
674  char cmd[UFOCMDLEN + 1];
675 
676  if (dir == UFOPSDIR)
677  snprintf(cmd, UFOCMDLEN + 1, "%s", UFOCSETSDIR);
678  else
679  snprintf(cmd, UFOCMDLEN + 1, "%s", UFOCSETRDIR);
680 
681  if (!sendCommand(cmd, resp))
682  return false;
683 
684  direction = dir;
685  return true;
686 }
687 
688 bool USBFocusV3::setMaxPos(unsigned int maxp)
689 {
690  char cmd[UFOCMDLEN + 1];
691  char resp[UFORESLEN] = {};
692 
693  if (maxp >= 1 && maxp <= 65535)
694  {
695  snprintf(cmd, UFOCMDLEN + 1, UFOCSETMAX, maxp);
696  }
697  else
698  {
699  LOGF_ERROR("Focuser max. pos. value %d out of bounds", maxp);
700  return false;
701  }
702 
703  if (!sendCommand(cmd, resp))
704  return false;
705 
706  if (strncmp(resp, UFORSDONE, strlen(UFORSDONE)) == 0)
707  {
708  maxpos = maxp;
709  FocusAbsPosN[0].max = maxpos;
710  return true;
711  }
712 
713  LOG_ERROR("setMaxPos error: did not receive DONE.");
714 
715  return false;
716 }
717 
718 bool USBFocusV3::setSpeed(unsigned short drvspeed)
719 {
720  char cmd[UFOCMDLEN + 1];
721  char resp[UFORESLEN] = {};
722 
723  unsigned int spd;
724 
725  switch (drvspeed)
726  {
727  case 3:
728  spd = UFOPSPDAV;
729  break;
730  case 2:
731  spd = UFOPSPDSL;
732  break;
733  case 1:
734  spd = UFOPSPDUS;
735  break;
736  default:
737  spd = UFOPSPDERR;
738  break;
739  }
740 
741  if (spd != UFOPSPDERR)
742  {
743  snprintf(cmd, UFOCMDLEN + 1, UFOCSETSPEED, spd);
744  }
745  else
746  {
747  LOGF_ERROR("Focuser speed value %d out of bounds", drvspeed);
748  return false;
749  }
750 
751  if (!sendCommand(cmd, resp))
752  return false;
753 
754  if (strncmp(resp, UFORSDONE, strlen(UFORSDONE)) == 0)
755  {
756  speed = spd;
757  return true;
758  }
759 
760  LOG_ERROR("setSpeed error: did not receive DONE.");
761 
762  return false;
763 }
764 
765 bool USBFocusV3::setTemperatureCompensation(bool enable)
766 {
767  char cmd[UFOCMDLEN + 1];
768  char resp[UFORESLEN] = {};
769 
770  if (enable)
771  snprintf(cmd, UFOCMDLEN + 1, "%s", UFOCSETAUTO);
772  else
773  snprintf(cmd, UFOCMDLEN + 1, "%s", UFOCSETMANU);
774 
775  if (!sendCommand(cmd, resp))
776  return false;
777 
778  return true;
779 }
780 
781 bool USBFocusV3::setTempCompSign(unsigned int sign)
782 {
783  char cmd[UFOCMDLEN + 1];
784  char resp[UFORESLEN] = {};
785 
786  snprintf(cmd, UFOCMDLEN + 1, UFOCSETSIGN, sign);
787 
788  if (!sendCommand(cmd, resp))
789  return false;
790 
791  if (strncmp(resp, UFORSDONE, strlen(UFORSDONE)) == 0)
792  {
793  return true;
794  }
795 
796  LOG_ERROR("setTempCompSign error: did not receive DONE.");
797 
798  return false;
799 }
800 
801 bool USBFocusV3::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
802 {
803  if (!dev || strcmp(dev, getDeviceName()))
804  return false;
805 
806  if (StepModeSP.isNameMatch(name))
807  {
808  int current_mode = StepModeSP.findOnSwitchIndex();
809  StepModeSP.update(states, names, n);
810  int target_mode = StepModeSP.findOnSwitchIndex();
811  if (current_mode != target_mode)
812  {
813  bool rc;
814  if (target_mode == UFOPHSTEPS)
815  rc = setStepMode(FOCUS_HALF_STEP);
816  else
817  rc = setStepMode(FOCUS_FULL_STEP);
818 
819  if (!rc)
820  {
821  StepModeSP.reset();
822  StepModeSP[current_mode].setState(ISS_ON);
823  StepModeSP.setState(IPS_ALERT);
824  StepModeSP.apply();
825  return false;
826  }
827  }
828  StepModeSP.setState(IPS_OK);
829  StepModeSP.apply();
830  return true;
831  }
832 
833  if (RotDirSP.isNameMatch(name))
834  {
835  int current_mode = RotDirSP.findOnSwitchIndex();
836  RotDirSP.update(states, names, n);
837  int target_mode = RotDirSP.findOnSwitchIndex();
838  if (current_mode != target_mode)
839  {
840  if (!setRotDir(target_mode))
841  {
842  RotDirSP.reset();
843  RotDirSP[current_mode].setState(ISS_ON);
844  RotDirSP.setState(IPS_ALERT);
845  RotDirSP.apply();
846  return false;
847  }
848  }
849 
850  RotDirSP.setState(IPS_OK);
851  RotDirSP.apply();
852  return true;
853  }
854 
855  if (TemperatureCompensateSP.isNameMatch(name))
856  {
857  int last_index = TemperatureCompensateSP.findOnSwitchIndex();
858  TemperatureCompensateSP.update(states, names, n);
859  int target_index = TemperatureCompensateSP.findOnSwitchIndex();
860 
861  if (last_index != target_index)
862  {
863  if (!setTemperatureCompensation((TemperatureCompensateSP[0].getState() == ISS_ON)))
864  {
865  TemperatureCompensateSP.setState(IPS_ALERT);
866  TemperatureCompensateSP.reset();
867  TemperatureCompensateSP[last_index].setState(ISS_ON);
868  TemperatureCompensateSP.apply();
869  return false;
870  }
871  }
872  TemperatureCompensateSP.setState(IPS_OK);
873  TemperatureCompensateSP.apply();
874  return true;
875  }
876 
877  if (TempCompSignSP.isNameMatch(name))
878  {
879  int current_mode = TempCompSignSP.findOnSwitchIndex();
880  TempCompSignSP.update(states, names, n);
881  int target_mode = TempCompSignSP.findOnSwitchIndex();
882  if (current_mode != target_mode)
883  {
884  if (!setTempCompSign(target_mode))
885  {
886  TempCompSignSP.reset();
887  TempCompSignSP[current_mode].setState(ISS_ON);
888  TempCompSignSP.setState(IPS_ALERT);
889  TempCompSignSP.apply();
890  return false;
891  }
892  }
893  TempCompSignSP.setState(IPS_OK);
894  TempCompSignSP.apply();
895  return true;
896  }
897 
898  if (ResetSP.isNameMatch(name))
899  {
900  ResetSP.reset();
901 
902  if (reset())
903  ResetSP.setState(IPS_OK);
904  else
905  ResetSP.setState(IPS_ALERT);
906 
907  ResetSP.apply();
908  return true;
909  }
910  return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
911 }
912 
913 bool USBFocusV3::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
914 {
915  if (!dev || strcmp(dev, getDeviceName()))
916  return false;
917 
918  if (MaxPositionNP.isNameMatch(name))
919  {
920  MaxPositionNP.update(values, names, n);
921  if (!setMaxPos(MaxPositionNP[0].getValue()))
922  {
923  MaxPositionNP.setState(IPS_ALERT);
924  MaxPositionNP.apply();
925  return false;
926  }
927  MaxPositionNP.setState(IPS_OK);
928  MaxPositionNP.apply();
929  return true;
930  }
931 
932  if (TemperatureSettingNP.isNameMatch(name))
933  {
934  TemperatureSettingNP.update(values, names, n);
935  if (!setAutoTempCompThreshold(TemperatureSettingNP[1].getValue()) ||
936  !setTemperatureCoefficient(TemperatureSettingNP[UFOPNSIGN].getValue()))
937  {
938  TemperatureSettingNP.setState(IPS_ALERT);
939  TemperatureSettingNP.apply();
940  return false;
941  }
942 
943  TemperatureSettingNP.setState(IPS_OK);
944  TemperatureSettingNP.apply();
945  return true;
946  }
947  return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
948 }
949 
950 void USBFocusV3::GetFocusParams()
951 {
953 
954  if (updatePosition())
955  IDSetNumber(&FocusAbsPosNP, nullptr);
956 
957  if (updateMaxPos())
958  {
959  MaxPositionNP.apply();
960  IDSetNumber(&FocusAbsPosNP, nullptr);
961  }
962 
963  if (updateTemperature())
964  TemperatureNP.apply();
965 
966  if (updateTempCompSettings())
967  TemperatureSettingNP.apply();
968 
969  if (updateTempCompSign())
970  TempCompSignSP.apply();
971 
972  if (updateSpeed())
973  IDSetNumber(&FocusSpeedNP, nullptr);
974 
975  if (updateStepMode())
976  StepModeSP.apply();
977 
978  if (updateRotDir())
979  RotDirSP.apply();
980 
981  if (updateFWversion())
982  FWversionNP.apply();
983 }
984 
986 {
987  if (!setSpeed(speed))
988  return false;
989 
990  currentSpeed = speed;
991 
993  IDSetNumber(&FocusSpeedNP, nullptr);
994 
995  return true;
996 }
997 
998 IPState USBFocusV3::MoveAbsFocuser(uint32_t targetTicks)
999 {
1000  long ticks;
1001 
1002  targetPos = targetTicks;
1003 
1004  ticks = targetPos - FocusAbsPosN[0].value;
1005 
1006  bool rc = false;
1007 
1008  if (ticks < 0)
1009  rc = MoveFocuserUF(FOCUS_INWARD, -ticks);
1010  else if (ticks > 0)
1011  rc = MoveFocuserUF(FOCUS_OUTWARD, ticks);
1012 
1013  if (!rc)
1014  return IPS_ALERT;
1015 
1017 
1018  return IPS_BUSY;
1019 }
1020 
1022 {
1023  uint32_t aticks;
1024 
1025  if ((dir == FOCUS_INWARD) && (ticks > FocusAbsPosN[0].value))
1026  {
1027  aticks = FocusAbsPosN[0].value;
1029  "Requested %u ticks but relative inward movement has been limited to %u ticks", ticks, aticks);
1030  ticks = aticks;
1031  }
1032  else if ((dir == FOCUS_OUTWARD) && ((FocusAbsPosN[0].value + ticks) > MaxPositionNP[0].getValue()))
1033  {
1034  aticks = MaxPositionNP[0].getValue() - FocusAbsPosN[0].value;
1036  "Requested %u ticks but relative outward movement has been limited to %u ticks", ticks, aticks);
1037  ticks = aticks;
1038  }
1039 
1040  if (!MoveFocuserUF(dir, (unsigned int)ticks))
1041  return IPS_ALERT;
1042 
1043  FocusRelPosN[0].value = ticks;
1045 
1046  return IPS_BUSY;
1047 }
1048 
1050 {
1051  if (!isConnected())
1052  {
1053  return;
1054  }
1055 
1056  if (updatePosition())
1057  {
1058  if (fabs(lastPos - FocusAbsPosN[0].value) > 5)
1059  {
1060  IDSetNumber(&FocusAbsPosNP, nullptr);
1061  lastPos = FocusAbsPosN[0].value;
1062  }
1063  }
1064 
1065  if (updateTemperature())
1066  {
1067  if (fabs(lastTemperature - TemperatureNP[0].getValue()) >= 0.5)
1068  {
1069  TemperatureNP.apply();
1070  lastTemperature = TemperatureNP[0].getValue();
1071  }
1072  }
1073 
1074  if (FocusTimerNP.s == IPS_BUSY)
1075  {
1076  float remaining = CalcTimeLeft(focusMoveStart, focusMoveRequest);
1077 
1078  if (remaining <= 0)
1079  {
1080  FocusTimerNP.s = IPS_OK;
1081  FocusTimerN[0].value = 0;
1082  AbortFocuser();
1083  }
1084  else
1085  FocusTimerN[0].value = remaining * 1000.0;
1086 
1087  IDSetNumber(&FocusTimerNP, nullptr);
1088  }
1089 
1091  {
1092  if (backlashMove && (fabs(backlashTargetPos - FocusAbsPosN[0].value) < 1))
1093  {
1094  // Backlash target reached, now go to real target
1095  MoveAbsFocuser(targetPos);
1096  backlashMove = false;
1097  }
1098  else
1099  {
1100  if (fabs(targetPos - FocusAbsPosN[0].value) < 1)
1101  {
1104  IDSetNumber(&FocusAbsPosNP, nullptr);
1105  IDSetNumber(&FocusRelPosNP, nullptr);
1106  lastPos = FocusAbsPosN[0].value;
1107  LOG_INFO("Focuser reached requested position.");
1108  }
1109  }
1110  }
1111 
1113 }
1114 
1116 {
1117  char resp[UFORESLEN] = {};
1118  if (!sendCommand(UFOCABORT, resp))
1119  return false;
1120 
1123  IDSetNumber(&FocusAbsPosNP, nullptr);
1124  IDSetNumber(&FocusRelPosNP, nullptr);
1125  backlashMove = false;
1126  moving = false;
1127  return true;
1128 }
1129 
1130 float USBFocusV3::CalcTimeLeft(timeval start, float req)
1131 {
1132  double timesince;
1133  double timeleft;
1134  struct timeval now
1135  {
1136  0, 0
1137  };
1138  gettimeofday(&now, nullptr);
1139 
1140  timesince =
1141  (double)(now.tv_sec * 1000.0 + now.tv_usec / 1000) - (double)(start.tv_sec * 1000.0 + start.tv_usec / 1000);
1142  timesince = timesince / 1000;
1143  timeleft = req - timesince;
1144  return timeleft;
1145 }
1146 
1148 {
1149  backlashIn = steps < 0;
1150  backlashSteps = std::abs(steps);
1151 
1152  return true;
1153 }
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
void setDefaultPollingPeriod(uint32_t msec)
setDefaultPollingPeriod Change the default polling period to call TimerHit() function in the driver.
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool loadConfig(bool silent=false, const char *property=nullptr)
Load the last saved configuration file.
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.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
void addDebugControl()
Add Debug control to the driver.
INumberVectorProperty FocusSpeedNP
INumberVectorProperty FocusAbsPosNP
INumberVectorProperty FocusRelPosNP
INumberVectorProperty FocusTimerNP
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.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool isNameMatch(const char *otherName) const
bool update(const double values[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool update(const ISState states[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition: usbfocusv3.cpp:801
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
bool getControllerStatus()
Definition: usbfocusv3.cpp:352
virtual bool AbortFocuser() override
AbortFocuser all focus motion.
@ FOCUS_FULL_STEP
Definition: usbfocusv3.h:85
@ FOCUS_HALF_STEP
Definition: usbfocusv3.h:84
virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveFocuser the focuser to an relative position.
virtual IPState MoveAbsFocuser(uint32_t targetTicks) override
MoveFocuser the focuser to an absolute position.
Definition: usbfocusv3.cpp:998
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition: usbfocusv3.cpp:913
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition: usbfocusv3.cpp:143
virtual bool SetFocuserBacklash(int32_t steps) override
SetFocuserBacklash Set the focuser backlash compensation value.
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Definition: usbfocusv3.cpp:50
virtual const char * getDefaultName() override
Definition: usbfocusv3.cpp:202
virtual bool Handshake() override
perform handshake with device to check communication
Definition: usbfocusv3.cpp:182
virtual bool SetFocuserSpeed(int speed) override
SetFocuserSpeed Set Focuser speed.
Definition: usbfocusv3.cpp:985
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
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_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition: indicom.c:424
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition: indicom.c:482
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition: indicom.c:1167
int tty_nread_section(int fd, char *buf, int nsize, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition: indicom.c:666
Implementations for common driver routines.
@ TTY_OK
Definition: indicom.h:150
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOGF_WARN(fmt,...)
Definition: indilogger.h:81
#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 DEBUGF(priority, msg,...)
Definition: indilogger.h:57
#define MAXRBUF
Definition: indiserver.cpp:102
__u8 cmd[4]
Definition: pwc-ioctl.h:2
#define USBFOCUSV3_TIMEOUT
Definition: usbfocusv3.cpp:33
#define UFOCMOVEOUT
Definition: usbfocusv3.h:32
#define UFOCRESET
Definition: usbfocusv3.h:47
#define UFOCSETMANU
Definition: usbfocusv3.h:46
#define UFOID
Definition: usbfocusv3.h:51
#define UFOCSETSPEED
Definition: usbfocusv3.h:36
#define UFOCSETFSTEPS
Definition: usbfocusv3.h:40
#define UFOCSETAUTO
Definition: usbfocusv3.h:45
#define UFORSACK
Definition: usbfocusv3.h:53
#define UFOPNSIGN
Definition: usbfocusv3.h:64
#define UFOCDEVID
Definition: usbfocusv3.h:29
#define UFOCMDLEN
Definition: usbfocusv3.h:72
#define UFOCREADTEMP
Definition: usbfocusv3.h:31
#define UFOCMOVEIN
Definition: usbfocusv3.h:33
#define UFOPRDIR
Definition: usbfocusv3.h:60
#define UFOCSETRDIR
Definition: usbfocusv3.h:39
#define UFOCSETSIGN
Definition: usbfocusv3.h:44
#define UFOCGETSIGN
Definition: usbfocusv3.h:43
#define UFOCSETTCTHR
Definition: usbfocusv3.h:37
#define UFORSDONE
Definition: usbfocusv3.h:55
#define UFOPSPDUS
Definition: usbfocusv3.h:69
#define UFORESLEN
Definition: usbfocusv3.h:71
#define UFOCSETMAX
Definition: usbfocusv3.h:35
#define UFOPFSTEPS
Definition: usbfocusv3.h:61
#define UFOPSDIR
Definition: usbfocusv3.h:59
#define UFOCSETSDIR
Definition: usbfocusv3.h:38
#define UFOPSPDAV
Definition: usbfocusv3.h:67
#define UFOPPSIGN
Definition: usbfocusv3.h:63
#define UFOPSPDERR
Definition: usbfocusv3.h:66
#define UFOCREADPOS
Definition: usbfocusv3.h:30
#define UFOCABORT
Definition: usbfocusv3.h:34
#define UFOCSETHSTEPS
Definition: usbfocusv3.h:41
#define UFOPHSTEPS
Definition: usbfocusv3.h:62
#define UFOCSETSTDEG
Definition: usbfocusv3.h:42
#define UFOCREADPARAM
Definition: usbfocusv3.h:28
#define UFOPSPDSL
Definition: usbfocusv3.h:68