Instrument Neutral Distributed Interface INDI  2.0.2
nightcrawler.cpp
Go to the documentation of this file.
1 /*
2  NightCrawler NightCrawler Focuser & Rotator
3  Copyright (C) 2017 Jasem Mutlaq (mutlaqja@ikarustech.com)
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 
21 #include "nightcrawler.h"
22 
23 #include "indicom.h"
25 
26 #include <cmath>
27 #include <cstring>
28 #include <memory>
29 #include <termios.h>
30 
31 #define NIGHTCRAWLER_TIMEOUT 3
32 #define NIGHTCRAWLER_THRESHOLD 0.1
33 
34 #define NC_25_STEPS 374920
35 #define NC_30_STEPS 444080
36 #define NC_35_STEPS 505960
37 
38 #define ROTATOR_TAB "Rotator"
39 #define AUX_TAB "Aux"
40 #define SETTINGS_TAB "Settings"
41 
42 // Well, it is time I name something, even if simple, after Tommy, my loyal German Shephard companion.
43 // By the time of writing this, he is almost 4 years old. Live long and prosper, my good boy!
44 // 2018-12-12 JM: Updated this driver today. Tommy passed away a couple of months ago. May he rest in peace. I miss you.
45 static std::unique_ptr<NightCrawler> tommyGoodBoy(new NightCrawler());
46 
48 {
49  setVersion(1, 5);
50 
51  // Can move in Absolute & Relative motions, can AbortFocuser motion, and has variable speed.
54 }
55 
57 {
59 
60  FocusSpeedN[0].min = 1;
61  FocusSpeedN[0].max = 1;
62  FocusSpeedN[0].value = 1;
63 
64  // Focus Sync
65  IUFillNumber(&SyncFocusN[0], "FOCUS_SYNC_OFFSET", "Ticks", "%.f", 0, 100000., 0., 0.);
66  IUFillNumberVector(&SyncFocusNP, SyncFocusN, 1, getDeviceName(), "FOCUS_SYNC", "Sync", MAIN_CONTROL_TAB, IP_RW, 0,
67  IPS_IDLE );
68 
69  // Voltage
70  IUFillNumber(&VoltageN[0], "VALUE", "Value (v)", "%.2f", 0, 30., 1., 0.);
71  IUFillNumberVector(&VoltageNP, VoltageN, 1, getDeviceName(), "Voltage", "Voltage", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE );
72 
73  // Temperature
74  IUFillNumber(&TemperatureN[0], "TEMPERATURE", "Value (C)", "%.2f", -100, 100., 1., 0.);
75  IUFillNumberVector(&TemperatureNP, TemperatureN, 1, getDeviceName(), "FOCUS_TEMPERATURE", "Temperature", MAIN_CONTROL_TAB,
76  IP_RO, 0, IPS_IDLE );
77 
78  // Temperature offset
79  IUFillNumber(&TemperatureOffsetN[0], "OFFSET", "Offset", "%.2f", -15, 15., 1., 0.);
80  IUFillNumberVector(&TemperatureOffsetNP, TemperatureOffsetN, 1, getDeviceName(), "TEMPERATURE_OFFSET", "Temperature",
82 
83  // Motor Step Delay
84  IUFillNumber(&FocusStepDelayN[0], "FOCUS_STEP", "Value", "%.f", 7, 100., 1., 7.);
85  IUFillNumberVector(&FocusStepDelayNP, FocusStepDelayN, 1, getDeviceName(), "FOCUS_STEP_DELAY", "Step Rate", SETTINGS_TAB,
86  IP_RW, 0, IPS_IDLE );
87 
88  // Limit Switch
89  IUFillLight(&LimitSwitchL[ROTATION_SWITCH], "ROTATION_SWITCH", "Rotation Home", IPS_OK);
90  IUFillLight(&LimitSwitchL[OUT_SWITCH], "OUT_SWITCH", "Focus Out Limit", IPS_OK);
91  IUFillLight(&LimitSwitchL[IN_SWITCH], "IN_SWITCH", "Focus In Limit", IPS_OK);
92  IUFillLightVector(&LimitSwitchLP, LimitSwitchL, 3, getDeviceName(), "LIMIT_SWITCHES", "Limit Switch", SETTINGS_TAB,
93  IPS_IDLE);
94 
95  // Home selection
96  IUFillSwitch(&HomeSelectionS[MOTOR_FOCUS], "FOCUS", "Focuser", ISS_ON);
97  IUFillSwitch(&HomeSelectionS[MOTOR_ROTATOR], "ROTATOR", "Rotator", ISS_ON);
98  IUFillSwitch(&HomeSelectionS[MOTOR_AUX], "AUX", "Aux", ISS_OFF);
99  IUFillSwitchVector(&HomeSelectionSP, HomeSelectionS, 3, getDeviceName(), "HOME_SELECTION", "Home Select", SETTINGS_TAB,
100  IP_RW, ISR_NOFMANY, 0, IPS_IDLE);
101 
102  // Home Find
103  IUFillSwitch(&FindHomeS[0], "FIND", "Start", ISS_OFF);
104  IUFillSwitchVector(&FindHomeSP, FindHomeS, 1, getDeviceName(), "FIND_HOME", "Home Find", SETTINGS_TAB, IP_RW, ISR_1OFMANY,
105  0, IPS_IDLE);
106 
107  // Encoders
108  IUFillSwitch(&EncoderS[INDI_ENABLED], "INDI_ENABLED", "Enabled", ISS_ON);
109  IUFillSwitch(&EncoderS[INDI_DISABLED], "INDI_DISABLED", "Disabled", ISS_OFF);
110  IUFillSwitchVector(&EncoderSP, EncoderS, 2, getDeviceName(), "ENCODERS", "Encoders", SETTINGS_TAB, IP_RW, ISR_1OFMANY, 0,
111  IPS_IDLE);
112 
113  // Brightness
114  IUFillNumber(&BrightnessN[BRIGHTNESS_DISPLAY], "BRIGHTNESS_DISPLAY", "Display", "%.f", 0, 255., 10., 150.);
115  IUFillNumber(&BrightnessN[BRIGHTNESS_SLEEP], "BRIGHTNESS_SLEEP", "Sleep", "%.f", 1, 255., 10., 16.);
116  IUFillNumberVector(&BrightnessNP, BrightnessN, 2, getDeviceName(), "BRIGHTNESS", "Brightness", SETTINGS_TAB, IP_RW, 0,
117  IPS_IDLE );
118 
120  // Rotator Properties
122 
124 
125  // Rotator Ticks
126  IUFillNumber(&RotatorAbsPosN[0], "ROTATOR_ABSOLUTE_POSITION", "Ticks", "%.f", 0., 100000., 1000., 0.);
127  IUFillNumberVector(&RotatorAbsPosNP, RotatorAbsPosN, 1, getDeviceName(), "ABS_ROTATOR_POSITION", "Goto", ROTATOR_TAB, IP_RW,
128  0, IPS_IDLE );
129 
130  // Rotator Step Delay
131  IUFillNumber(&RotatorStepDelayN[0], "ROTATOR_STEP", "Value", "%.f", 7, 100., 1., 7.);
132  IUFillNumberVector(&RotatorStepDelayNP, RotatorStepDelayN, 1, getDeviceName(), "ROTATOR_STEP_DELAY", "Step Rate",
133  ROTATOR_TAB, IP_RW, 0, IPS_IDLE );
134 
135  // For custom focuser, set max steps
136  CustomRotatorStepNP[0].fill("STEPS", "Steps", "%.f", 0, 5000000, 0, 0);
137  CustomRotatorStepNP.fill(getDeviceName(), "CUSTOM_STEPS", "Custom steps", ROTATOR_TAB, IP_RW, 60, IPS_IDLE);
138 
140  // Aux Properties
142 
143  // Aux GOTO
144  IUFillNumber(&GotoAuxN[0], "AUX_ABSOLUTE_POSITION", "Ticks", "%.f", 0, 100000., 0., 0.);
145  IUFillNumberVector(&GotoAuxNP, GotoAuxN, 1, getDeviceName(), "ABS_AUX_POSITION", "Goto", AUX_TAB, IP_RW, 0, IPS_IDLE );
146 
147  // Abort Aux
148  IUFillSwitch(&AbortAuxS[0], "ABORT", "Abort", ISS_OFF);
149  IUFillSwitchVector(&AbortAuxSP, AbortAuxS, 1, getDeviceName(), "AUX_ABORT_MOTION", "Abort Motion", AUX_TAB, IP_RW,
150  ISR_ATMOST1, 0, IPS_IDLE);
151 
152  // Aux Sync
153  IUFillNumber(&SyncAuxN[0], "AUX_SYNC_TICK", "Ticks", "%.f", 0, 100000., 0., 0.);
154  IUFillNumberVector(&SyncAuxNP, SyncAuxN, 1, getDeviceName(), "SYNC_AUX", "Sync", AUX_TAB, IP_RW, 0, IPS_IDLE );
155 
156  // Aux Step Delay
157  IUFillNumber(&AuxStepDelayN[0], "AUX_STEP", "Value", "%.f", 7, 100., 1., 7.);
158  IUFillNumberVector(&AuxStepDelayNP, AuxStepDelayN, 1, getDeviceName(), "AUX_STEP_DELAY", "Step Rate", AUX_TAB, IP_RW, 0,
159  IPS_IDLE );
160 
161  /* Relative and absolute movement */
162  FocusRelPosN[0].min = 0.;
163  FocusRelPosN[0].max = 50000.;
164  FocusRelPosN[0].value = 0;
165  FocusRelPosN[0].step = 1000;
166 
167  FocusAbsPosN[0].min = 0.;
168  FocusAbsPosN[0].max = 100000.;
169  FocusAbsPosN[0].value = 0;
170  FocusAbsPosN[0].step = 1000;
171 
172  addDebugControl();
173 
175 
177 
179 
180  return true;
181 }
182 
184 {
186 
187  if (isConnected())
188  {
189  // Focus
190  defineProperty(&SyncFocusNP);
191  defineProperty(&VoltageNP);
192  defineProperty(&TemperatureNP);
193  defineProperty(&TemperatureOffsetNP);
194  defineProperty(&FocusStepDelayNP);
195  defineProperty(&LimitSwitchLP);
196  defineProperty(&EncoderSP);
197  defineProperty(&BrightnessNP);
198  defineProperty(&HomeSelectionSP);
199  defineProperty(&FindHomeSP);
200 
201  // Rotator
203  defineProperty(&RotatorAbsPosNP);
204  defineProperty(&RotatorStepDelayNP);
205  defineProperty(CustomRotatorStepNP);
206 
207  // Aux
208  defineProperty(&GotoAuxNP);
209  defineProperty(&AbortAuxSP);
210  defineProperty(&SyncAuxNP);
211  defineProperty(&AuxStepDelayNP);
212  }
213  else
214  {
215  // Focus
216  deleteProperty(SyncFocusNP.name);
217  deleteProperty(VoltageNP.name);
218  deleteProperty(TemperatureNP.name);
219  deleteProperty(TemperatureOffsetNP.name);
220  deleteProperty(FocusStepDelayNP.name);
221  deleteProperty(LimitSwitchLP.name);
222  deleteProperty(EncoderSP.name);
223  deleteProperty(BrightnessNP.name);
224  deleteProperty(FindHomeSP.name);
225  deleteProperty(HomeSelectionSP.name);
226 
227  // Rotator
229  deleteProperty(RotatorAbsPosNP.name);
230  deleteProperty(RotatorStepDelayNP.name);
231  deleteProperty(CustomRotatorStepNP);
232 
233  // Aux
234  deleteProperty(GotoAuxNP.name);
235  deleteProperty(AbortAuxSP.name);
236  deleteProperty(SyncAuxNP.name);
237  deleteProperty(AuxStepDelayNP.name);
238  }
239 
240  return true;
241 }
242 
244 {
245  if (Ack())
246  return true;
247 
248  LOG_INFO("Error retrieving data from NightCrawler, please ensure NightCrawler controller is powered and the port is correct.");
249  return false;
250 }
251 
253 {
254  return "NightCrawler";
255 }
256 
257 bool NightCrawler::Ack()
258 {
259  bool rcFirmware = getFirmware();
260  bool rcType = getFocuserType();
261 
262  return (rcFirmware && rcType);
263 }
264 
265 bool NightCrawler::getFirmware()
266 {
267  int nbytes_written = 0, nbytes_read = 0, rc = -1;
268  char errstr[MAXRBUF];
269  char resp[64];
270 
271  tcflush(PortFD, TCIOFLUSH);
272 
273  if ( (rc = tty_write(PortFD, "PV#", 3, &nbytes_written)) != TTY_OK)
274  {
275  tty_error_msg(rc, errstr, MAXRBUF);
276  LOGF_ERROR("getFirmware error: %s.", errstr);
277  return false;
278  }
279 
280  if ( (rc = tty_read_section(PortFD, resp, '#', NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
281  {
282  tty_error_msg(rc, errstr, MAXRBUF);
283  LOGF_ERROR("getFirmware error: %s.", errstr);
284  return false;
285  }
286 
287  tcflush(PortFD, TCIOFLUSH);
288 
289  resp[nbytes_read - 1] = '\0';
290 
291  LOGF_INFO("Firmware %s", resp);
292 
293  return true;
294 }
295 
296 bool NightCrawler::getFocuserType()
297 {
298  int nbytes_written = 0, nbytes_read = 0, rc = -1;
299  char errstr[MAXRBUF];
300  char resp[64];
301 
302  tcflush(PortFD, TCIOFLUSH);
303 
304  if ( (rc = tty_write(PortFD, "PF#", 3, &nbytes_written)) != TTY_OK)
305  {
306  tty_error_msg(rc, errstr, MAXRBUF);
307  LOGF_ERROR("getFirmware error: %s.", errstr);
308  return false;
309  }
310 
311  if ( (rc = tty_read_section(PortFD, resp, '#', NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
312  {
313  tty_error_msg(rc, errstr, MAXRBUF);
314  LOGF_ERROR("getFirmware error: %s.", errstr);
315  return false;
316  }
317 
318  tcflush(PortFD, TCIOFLUSH);
319 
320  resp[nbytes_read - 1] = '\0';
321 
322  LOGF_INFO("Focuser Type %s", resp);
323 
324  if (strcmp(resp, "2.5 NC") == 0)
325  {
326  RotatorAbsPosN[0].min = -NC_25_STEPS / 2.0;
327  RotatorAbsPosN[0].max = NC_25_STEPS / 2.0;
328  m_RotatorStepsPerRevolution = NC_25_STEPS;
329  }
330  else if (strcmp(resp, "3.0 NC") == 0)
331  {
332  RotatorAbsPosN[0].min = -NC_30_STEPS / 2.0;
333  RotatorAbsPosN[0].max = NC_30_STEPS / 2.0;
334  m_RotatorStepsPerRevolution = NC_30_STEPS;
335  }
336  else
337  {
338  RotatorAbsPosN[0].min = -NC_35_STEPS / 2.0;
339  RotatorAbsPosN[0].max = NC_35_STEPS / 2.0;
340  m_RotatorStepsPerRevolution = NC_35_STEPS;
341  }
342 
343  m_RotatorTicksPerDegree = m_RotatorStepsPerRevolution / 360.0;
344 
345  return true;
346 }
347 
348 bool NightCrawler::gotoMotor(MotorType type, int32_t position)
349 {
350  char cmd[16] = {0};
351  char res[16] = {0};
352 
353  int nbytes_written = 0, nbytes_read = 0, rc = -1;
354  char errstr[MAXRBUF];
355 
356  snprintf(cmd, 16, "%dSN %d#", type + 1, position);
357 
358  LOGF_DEBUG("CMD <%s>", cmd);
359 
360  tcflush(PortFD, TCIOFLUSH);
361 
362  if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
363  {
364  tty_error_msg(rc, errstr, MAXRBUF);
365  LOGF_ERROR("%s: %s.", __FUNCTION__, errstr);
366  return false;
367  }
368 
369  if ( (rc = tty_read(PortFD, res, 1, NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
370  {
371  tty_error_msg(rc, errstr, MAXRBUF);
372  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
373  return false;
374  }
375 
376  LOGF_DEBUG("RES <%s>", res);
377 
378  return startMotor(type);
379 }
380 
381 bool NightCrawler::getPosition(MotorType type)
382 {
383  char cmd[16] = {0};
384  char res[16] = {0};
385  int position = -1e6;
386 
387  int nbytes_written = 0, nbytes_read = 0, rc = -1;
388  char errstr[MAXRBUF];
389 
390  snprintf(cmd, 16, "%dGP#", type + 1);
391 
392  LOGF_DEBUG("CMD <%s>", cmd);
393 
394  tcflush(PortFD, TCIOFLUSH);
395 
396  if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
397  {
398  // tty_error_msg(rc, errstr, MAXRBUF);
399  // LOGF_ERROR("%s: %s.", __FUNCTION__, errstr);
400  // return false;
401  abnormalDisconnect();
402  return false;
403  }
404 
405  if ( (rc = tty_read(PortFD, res, 8, NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
406  {
407  tty_error_msg(rc, errstr, MAXRBUF);
408  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
409  return false;
410  }
411 
412  res[nbytes_read] = '\0';
413 
414  LOGF_DEBUG("RES <%s>", res);
415 
416  position = atoi(res);
417 
418  if (position != -1e6)
419  {
420  if (type == MOTOR_FOCUS)
421  FocusAbsPosN[0].value = position;
422  else if (type == MOTOR_ROTATOR)
423  RotatorAbsPosN[0].value = position;
424  else
425  GotoAuxN[0].value = position;
426 
427  return true;
428  }
429 
430  LOGF_DEBUG("Invalid Position! %d", position);
431  return false;
432 }
433 
435 {
436  NightCrawler *p = static_cast<NightCrawler *>(userpointer);
437  if (p->Connect())
438  {
439  p->setConnected(true, IPS_OK);
440  p->updateProperties();
441  }
442 }
443 
444 void NightCrawler::abnormalDisconnect()
445 {
446  // Ignore disconnect errors
447  Disconnect();
448 
449  // Set Disconnected
450  setConnected(false, IPS_IDLE);
451  // Update properties
453 
454  // Reconnect in 2 seconds
456 }
457 
458 bool NightCrawler::ISNewSwitch (const char * dev, const char * name, ISState * states, char * names[], int n)
459 {
460  if(strcmp(dev, getDeviceName()) == 0)
461  {
462  if (strcmp(name, HomeSelectionSP.name) == 0)
463  {
464  bool atLeastOne = false;
465 
466  for (int i = 0; i < n; i++)
467  {
468  if (states[i] == ISS_ON)
469  {
470  atLeastOne = true;
471  break;
472  }
473  }
474 
475  if (!atLeastOne)
476  {
477  HomeSelectionSP.s = IPS_ALERT;
478  LOG_ERROR("At least one selection must be on.");
479  IDSetSwitch(&HomeSelectionSP, nullptr);
480  return false;
481  }
482 
483  IUUpdateSwitch(&HomeSelectionSP, states, names, n);
484  HomeSelectionSP.s = IPS_OK;
485  IDSetSwitch(&HomeSelectionSP, nullptr);
486  return true;
487  }
488  else if (strcmp(name, FindHomeSP.name) == 0)
489  {
490  uint8_t selection = 0;
491 
492  if (HomeSelectionS[MOTOR_FOCUS].s == ISS_ON)
493  selection |= 0x01;
494  if (HomeSelectionS[MOTOR_ROTATOR].s == ISS_ON)
495  selection |= 0x02;
496  if (HomeSelectionS[MOTOR_AUX].s == ISS_ON)
497  selection |= 0x04;
498 
499  if (findHome(selection))
500  {
501  FindHomeSP.s = IPS_BUSY;
502  FindHomeS[0].s = ISS_ON;
503  LOG_WARN("Homing process can take up to 10 minutes. You cannot control the unit until the process is fully complete.");
504  }
505  else
506  {
507  FindHomeSP.s = IPS_ALERT;
508  FindHomeS[0].s = ISS_OFF;
509  LOG_ERROR("Failed to start homing process.");
510  }
511 
512  IDSetSwitch(&FindHomeSP, nullptr);
513  return true;
514  }
515  else if (strcmp(name, EncoderSP.name) == 0)
516  {
517  IUUpdateSwitch(&EncoderSP, states, names, n);
518  EncoderSP.s = setEncodersEnabled(EncoderS[0].s == ISS_ON) ? IPS_OK : IPS_ALERT;
519  if (EncoderSP.s == IPS_OK)
520  LOGF_INFO("Encoders are %s", (EncoderS[0].s == ISS_ON) ? "ON" : "OFF");
521  IDSetSwitch(&EncoderSP, nullptr);
522  return true;
523  }
524  else if (strcmp(name, AbortAuxSP.name) == 0)
525  {
526  AbortAuxSP.s = stopMotor(MOTOR_AUX) ? IPS_OK : IPS_ALERT;
527  IDSetSwitch(&AbortAuxSP, nullptr);
528  if (AbortAuxSP.s == IPS_OK)
529  {
530  if (GotoAuxNP.s != IPS_OK)
531  {
532  GotoAuxNP.s = IPS_OK;
533  IDSetNumber(&GotoAuxNP, nullptr);
534  }
535  }
536  return true;
537  }
538  else if (strstr(name, "ROTATOR"))
539  {
540  if (INDI::RotatorInterface::processSwitch(dev, name, states, names, n))
541  return true;
542  }
543  }
544 
545  return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
546 }
547 
548 bool NightCrawler::ISNewNumber (const char * dev, const char * name, double values[], char * names[], int n)
549 {
550  if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
551  {
552  if (strcmp(name, SyncFocusNP.name) == 0)
553  {
554  bool rc = syncMotor(MOTOR_FOCUS, static_cast<uint32_t>(values[0]));
555  SyncFocusNP.s = rc ? IPS_OK : IPS_ALERT;
556  if (rc)
557  SyncFocusN[0].value = values[0];
558 
559  IDSetNumber(&SyncFocusNP, nullptr);
560  return true;
561  }
562  else if (strcmp(name, SyncAuxNP.name) == 0)
563  {
564  bool rc = syncMotor(MOTOR_AUX, static_cast<uint32_t>(values[0]));
565  SyncAuxNP.s = rc ? IPS_OK : IPS_ALERT;
566  if (rc)
567  SyncAuxN[0].value = values[0];
568 
569  IDSetNumber(&SyncAuxNP, nullptr);
570  return true;
571  }
572  else if (strcmp(name, TemperatureOffsetNP.name) == 0)
573  {
574  bool rc = setTemperatureOffset(values[0]);
575  TemperatureOffsetNP.s = rc ? IPS_OK : IPS_ALERT;
576  IDSetNumber(&TemperatureOffsetNP, nullptr);
577  return true;
578  }
579  else if (strcmp(name, FocusStepDelayNP.name) == 0)
580  {
581  bool rc = setStepDelay(MOTOR_FOCUS, static_cast<uint32_t>(values[0]));
582  FocusStepDelayNP.s = rc ? IPS_OK : IPS_ALERT;
583  if (rc)
584  FocusStepDelayN[0].value = values[0];
585  IDSetNumber(&FocusStepDelayNP, nullptr);
586  return true;
587  }
588  else if (strcmp(name, RotatorStepDelayNP.name) == 0)
589  {
590  bool rc = setStepDelay(MOTOR_ROTATOR, static_cast<uint32_t>(values[0]));
591  RotatorStepDelayNP.s = rc ? IPS_OK : IPS_ALERT;
592  if (rc)
593  RotatorStepDelayN[0].value = values[0];
594  IDSetNumber(&RotatorStepDelayNP, nullptr);
595  return true;
596  }
597  else if (strcmp(name, AuxStepDelayNP.name) == 0)
598  {
599  bool rc = setStepDelay(MOTOR_AUX, static_cast<uint32_t>(values[0]));
600  AuxStepDelayNP.s = rc ? IPS_OK : IPS_ALERT;
601  if (rc)
602  AuxStepDelayN[0].value = values[0];
603  IDSetNumber(&AuxStepDelayNP, nullptr);
604  return true;
605  }
606  else if (strcmp(name, BrightnessNP.name) == 0)
607  {
608  IUUpdateNumber(&BrightnessNP, values, names, n);
609  bool rcDisplay = setDisplayBrightness(static_cast<uint8_t>(BrightnessN[BRIGHTNESS_DISPLAY].value));
610  bool rcSleep = setSleepBrightness(static_cast<uint8_t>(BrightnessN[BRIGHTNESS_SLEEP].value));
611  if (rcDisplay && rcSleep)
612  BrightnessNP.s = IPS_OK;
613  else
614  BrightnessNP.s = IPS_ALERT;
615 
616  IDSetNumber(&BrightnessNP, nullptr);
617  return true;
618  }
619  else if (strcmp(name, GotoAuxNP.name) == 0)
620  {
621  bool rc = gotoMotor(MOTOR_AUX, static_cast<int32_t>(values[0]));
622  GotoAuxNP.s = rc ? IPS_BUSY : IPS_OK;
623  IDSetNumber(&GotoAuxNP, nullptr);
624  LOGF_INFO("Aux moving to %.f...", values[0]);
625  return true;
626  }
627  else if (strcmp(name, RotatorAbsPosNP.name) == 0)
628  {
629  RotatorAbsPosNP.s = (gotoMotor(MOTOR_ROTATOR, static_cast<int32_t>(values[0])) ? IPS_BUSY : IPS_ALERT);
630  IDSetNumber(&RotatorAbsPosNP, nullptr);
631  if (RotatorAbsPosNP.s == IPS_BUSY)
632  LOGF_INFO("Rotator moving to %.f ticks...", values[0]);
633  return true;
634  }
635  else if (CustomRotatorStepNP.isNameMatch(name))
636  {
637  CustomRotatorStepNP.update(values, names, n);
638  CustomRotatorStepNP.setState(IPS_OK);
639  CustomRotatorStepNP.apply();
640 
641  auto customValue = CustomRotatorStepNP[0].getValue();
642  if (customValue > 0)
643  {
644  RotatorAbsPosN[0].min = customValue / 2.0;
645  RotatorAbsPosN[0].max = customValue / 2.0;
646  m_RotatorStepsPerRevolution = customValue;
647  m_RotatorTicksPerDegree = m_RotatorStepsPerRevolution / 360.0;
648  IUUpdateMinMax(&RotatorAbsPosNP);
649 
650  LOGF_INFO("Custom steps per revolution updated to %.f. Ticks per degree %.2f", customValue,
651  m_RotatorTicksPerDegree);
652  }
653  saveConfig(CustomRotatorStepNP);
654  return true;
655  }
656  else if (strstr(name, "ROTATOR"))
657  {
658  if (INDI::RotatorInterface::processNumber(dev, name, values, names, n))
659  return true;
660  }
661  }
662 
663  return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
664 }
665 
667 {
668  targetPosition = targetTicks;
669 
670  bool rc = false;
671 
672  rc = gotoMotor(MOTOR_FOCUS, targetPosition);
673 
674  if (!rc)
675  return IPS_ALERT;
676 
678 
679  return IPS_BUSY;
680 }
681 
683 {
684  double newPosition = 0;
685  bool rc = false;
686 
687  if (dir == FOCUS_INWARD)
688  newPosition = FocusAbsPosN[0].value - ticks;
689  else
690  newPosition = FocusAbsPosN[0].value + ticks;
691 
692  rc = gotoMotor(MOTOR_FOCUS, newPosition);
693 
694  if (!rc)
695  return IPS_ALERT;
696 
697  FocusRelPosN[0].value = ticks;
699 
700  return IPS_BUSY;
701 }
702 
704 {
705  if (!isConnected())
706  {
708  return;
709  }
710 
711  bool rc = false;
712 
713  // #1 If we're homing, we check if homing is complete as we cannot check for anything else
714  if (FindHomeSP.s == IPS_BUSY || HomeRotatorSP.s == IPS_BUSY)
715  {
716  if (isHomingComplete())
717  {
718  HomeRotatorS[0].s = ISS_OFF;
720  IDSetSwitch(&HomeRotatorSP, nullptr);
721 
722  FindHomeS[0].s = ISS_OFF;
723  FindHomeSP.s = IPS_OK;
724  IDSetSwitch(&FindHomeSP, nullptr);
725 
726  LOG_INFO("Homing is complete.");
727  }
728 
730  return;
731  }
732 
733  // #2 Get Temperature
734  rc = getTemperature();
735  if (rc && std::abs(TemperatureN[0].value - lastTemperature) > NIGHTCRAWLER_THRESHOLD)
736  {
737  lastTemperature = TemperatureN[0].value;
738  IDSetNumber(&TemperatureNP, nullptr);
739  }
740 
741  // #3 Get Voltage
742  rc = getVoltage();
743  if (rc && std::abs(VoltageN[0].value - lastVoltage) > NIGHTCRAWLER_THRESHOLD)
744  {
745  lastVoltage = VoltageN[0].value;
746  IDSetNumber(&VoltageNP, nullptr);
747  }
748 
749  // #4 Get Limit Switch Status
750  rc = getLimitSwitchStatus();
751  if (rc && (LimitSwitchL[ROTATION_SWITCH].s != rotationLimit || LimitSwitchL[OUT_SWITCH].s != outSwitchLimit
752  || LimitSwitchL[IN_SWITCH].s != inSwitchLimit))
753  {
754  rotationLimit = LimitSwitchL[ROTATION_SWITCH].s;
755  outSwitchLimit = LimitSwitchL[OUT_SWITCH].s;
756  inSwitchLimit = LimitSwitchL[IN_SWITCH].s;
757  IDSetLight(&LimitSwitchLP, nullptr);
758  }
759 
760  // #5 Focus Position & Status
761  bool absFocusUpdated = false;
762 
763  if (FocusAbsPosNP.s == IPS_BUSY)
764  {
765  // Stopped moving
766  if (!isMotorMoving(MOTOR_FOCUS))
767  {
769  if (FocusRelPosNP.s != IPS_OK)
770  {
772  IDSetNumber(&FocusRelPosNP, nullptr);
773  }
774  absFocusUpdated = true;
775  }
776  }
777  rc = getPosition(MOTOR_FOCUS);
778  if (rc && std::abs(FocusAbsPosN[0].value - lastFocuserPosition) > NIGHTCRAWLER_THRESHOLD)
779  {
780  lastFocuserPosition = FocusAbsPosN[0].value;
781  absFocusUpdated = true;
782  }
783  if (absFocusUpdated)
784  IDSetNumber(&FocusAbsPosNP, nullptr);
785 
786  // #6 Rotator Position & Status
787  bool absRotatorUpdated = false;
788 
789  if (RotatorAbsPosNP.s == IPS_BUSY)
790  {
791  // Stopped moving
792  if (!isMotorMoving(MOTOR_ROTATOR))
793  {
794  RotatorAbsPosNP.s = IPS_OK;
796  absRotatorUpdated = true;
797  LOG_INFO("Rotator motion complete.");
798  }
799  }
800  rc = getPosition(MOTOR_ROTATOR);
801  // If absolute position is zero, we must sync to 180 degrees so we can rotate in both directions freely.
802  // Also sometimes Rotator motor returns negative result, we must sync it to 180 degrees as well.
803  while (RotatorAbsPosN[0].value < -m_RotatorStepsPerRevolution || RotatorAbsPosN[0].value > m_RotatorStepsPerRevolution)
804  {
805  // Update value to take care of multiple rotations.
806  const auto newOffset = static_cast<int32_t>(RotatorAbsPosN[0].value) % m_RotatorStepsPerRevolution;
807  LOGF_INFO("Out of bounds value detected. Syncing rotator position to %d", newOffset);
808  syncMotor(MOTOR_ROTATOR, newOffset);
809  rc = getPosition(MOTOR_ROTATOR);
810  }
811  if (rc && std::abs(RotatorAbsPosN[0].value - lastRotatorPosition) > NIGHTCRAWLER_THRESHOLD)
812  {
813  lastRotatorPosition = RotatorAbsPosN[0].value;
815  GotoRotatorN[0].value = range360(360 - (RotatorAbsPosN[0].value / m_RotatorTicksPerDegree));
816  else
817  GotoRotatorN[0].value = range360(RotatorAbsPosN[0].value / m_RotatorTicksPerDegree);
818  absRotatorUpdated = true;
819  }
820  if (absRotatorUpdated)
821  {
822  IDSetNumber(&RotatorAbsPosNP, nullptr);
823  IDSetNumber(&GotoRotatorNP, nullptr);
824  }
825 
826  // #7 Aux Position & Status
827  bool absAuxUpdated = false;
828 
829  if (GotoAuxNP.s == IPS_BUSY)
830  {
831  // Stopped moving
832  if (!isMotorMoving(MOTOR_AUX))
833  {
834  GotoAuxNP.s = IPS_OK;
835  absAuxUpdated = true;
836  LOG_INFO("Aux motion complete.");
837  }
838  }
839  rc = getPosition(MOTOR_AUX);
840  if (rc && std::abs(GotoAuxN[0].value - lastAuxPosition) > NIGHTCRAWLER_THRESHOLD)
841  {
842  lastAuxPosition = GotoAuxN[0].value;
843  absAuxUpdated = true;
844  }
845  if (absAuxUpdated)
846  IDSetNumber(&GotoAuxNP, nullptr);
847 
849 }
850 
852 {
853  return stopMotor(MOTOR_FOCUS);
854 }
855 
856 bool NightCrawler::syncMotor(MotorType type, uint32_t position)
857 {
858  char cmd[16] = {0};
859  char res[16] = {0};
860 
861  int nbytes_written = 0, nbytes_read = 0, rc = -1;
862  char errstr[MAXRBUF];
863 
864  snprintf(cmd, 16, "%dSP %d#", type + 1, position);
865 
866  LOGF_DEBUG("CMD <%s>", cmd);
867 
868  tcflush(PortFD, TCIOFLUSH);
869 
870  if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
871  {
872  tty_error_msg(rc, errstr, MAXRBUF);
873  LOGF_ERROR("%s: %s.", __FUNCTION__, errstr);
874  return false;
875  }
876 
877  if ( (rc = tty_read(PortFD, res, 1, NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
878  {
879  tty_error_msg(rc, errstr, MAXRBUF);
880  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
881  return false;
882  }
883 
884  res[nbytes_read] = '\0';
885 
886  LOGF_DEBUG("RES <%s>", res);
887 
888  return (res[0] == '#');
889 }
890 
891 bool NightCrawler::startMotor(MotorType type)
892 {
893  char cmd[16] = {0};
894  char res[16] = {0};
895 
896  int nbytes_written = 0, nbytes_read = 0, rc = -1;
897  char errstr[MAXRBUF];
898 
899  snprintf(cmd, 16, "%dSM#", type + 1);
900 
901  LOGF_DEBUG("CMD <%s>", cmd);
902 
903  tcflush(PortFD, TCIOFLUSH);
904 
905  if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
906  {
907  tty_error_msg(rc, errstr, MAXRBUF);
908  LOGF_ERROR("%s: %s.", __FUNCTION__, errstr);
909  return false;
910  }
911 
912  if ( (rc = tty_read(PortFD, res, 1, NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
913  {
914  tty_error_msg(rc, errstr, MAXRBUF);
915  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
916  return false;
917  }
918 
919  res[nbytes_read] = '\0';
920 
921  LOGF_DEBUG("RES <%s>", res);
922 
923  return (res[0] == '#');
924 }
925 
926 bool NightCrawler::stopMotor(MotorType type)
927 {
928  char cmd[16] = {0};
929  char res[16] = {0};
930 
931  int nbytes_written = 0, nbytes_read = 0, rc = -1;
932  char errstr[MAXRBUF];
933 
934  snprintf(cmd, 16, "%dSQ#", type + 1);
935 
936  LOGF_DEBUG("CMD <%s>", cmd);
937 
938  tcflush(PortFD, TCIOFLUSH);
939 
940  if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
941  {
942  tty_error_msg(rc, errstr, MAXRBUF);
943  LOGF_ERROR("%s: %s.", __FUNCTION__, errstr);
944  return false;
945  }
946 
947  if ( (rc = tty_read(PortFD, res, 1, NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
948  {
949  tty_error_msg(rc, errstr, MAXRBUF);
950  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
951  return false;
952  }
953 
954  res[nbytes_read] = '\0';
955 
956  LOGF_DEBUG("RES <%s>", res);
957 
958  return (res[0] == '#');
959 }
960 
961 bool NightCrawler::isMotorMoving(MotorType type)
962 {
963  char cmd[16] = {0};
964  char res[16] = {0};
965 
966  int nbytes_written = 0, nbytes_read = 0, rc = -1;
967  char errstr[MAXRBUF];
968 
969  snprintf(cmd, 16, "%dGM#", type + 1);
970 
971  LOGF_DEBUG("CMD <%s>", cmd);
972 
973  tcflush(PortFD, TCIOFLUSH);
974 
975  if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
976  {
977  tty_error_msg(rc, errstr, MAXRBUF);
978  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
979  return false;
980  }
981 
982  if ( (rc = tty_read_section(PortFD, res, '#', NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
983  {
984  tty_error_msg(rc, errstr, MAXRBUF);
985  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
986  return false;
987  }
988 
989  res[nbytes_read - 1] = '\0';
990 
991  LOGF_DEBUG("RES <%s>", res);
992 
993  return (strcmp("01", res) == 0);
994 }
995 
996 bool NightCrawler::getTemperature()
997 {
998  char cmd[16] = "GT#";
999  char res[16] = {0};
1000 
1001  int nbytes_written = 0, nbytes_read = 0, rc = -1;
1002  char errstr[MAXRBUF];
1003 
1004  LOGF_DEBUG("CMD <%s>", cmd);
1005 
1006  tcflush(PortFD, TCIOFLUSH);
1007 
1008  if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1009  {
1010  tty_error_msg(rc, errstr, MAXRBUF);
1011  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1012  return false;
1013  }
1014 
1015  if ( (rc = tty_read_section(PortFD, res, '#', NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1016  {
1017  tty_error_msg(rc, errstr, MAXRBUF);
1018  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1019  return false;
1020  }
1021 
1022  res[nbytes_read - 1] = '\0';
1023 
1024  LOGF_DEBUG("RES <%s>", res);
1025 
1026  TemperatureN[0].value = atoi(res) / 10.0;
1027 
1028  return true;
1029 }
1030 
1031 bool NightCrawler::getVoltage()
1032 {
1033  char cmd[16] = "GV#";
1034  char res[16] = {0};
1035 
1036  int nbytes_written = 0, nbytes_read = 0, rc = -1;
1037  char errstr[MAXRBUF];
1038 
1039  LOGF_DEBUG("CMD <%s>", cmd);
1040 
1041  tcflush(PortFD, TCIOFLUSH);
1042 
1043  if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1044  {
1045  tty_error_msg(rc, errstr, MAXRBUF);
1046  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1047  return false;
1048  }
1049 
1050  if ( (rc = tty_read_section(PortFD, res, '#', NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1051  {
1052  tty_error_msg(rc, errstr, MAXRBUF);
1053  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1054  return false;
1055  }
1056 
1057  res[nbytes_read - 1] = '\0';
1058 
1059  LOGF_DEBUG("RES <%s>", res);
1060 
1061  VoltageN[0].value = atoi(res) / 10.0;
1062 
1063  return true;
1064 }
1065 
1066 bool NightCrawler::setTemperatureOffset(double offset)
1067 {
1068  char cmd[16] = {0};
1069 
1070  int nbytes_written = 0, rc = -1;
1071  char errstr[MAXRBUF];
1072 
1073  snprintf(cmd, 16, "Pt %03d#", static_cast<int>(offset * 10));
1074 
1075  LOGF_DEBUG("CMD <%s>", cmd);
1076 
1077  tcflush(PortFD, TCIOFLUSH);
1078 
1079  if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1080  {
1081  tty_error_msg(rc, errstr, MAXRBUF);
1082  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1083  return false;
1084  }
1085 
1086  return true;
1087 }
1088 
1089 bool NightCrawler::getStepDelay(MotorType type)
1090 {
1091  char cmd[16] = {0};
1092  char res[16] = {0};
1093 
1094  int nbytes_written = 0, nbytes_read = 0, rc = -1;
1095  char errstr[MAXRBUF];
1096 
1097  snprintf(cmd, 16, "%dSR#", type + 1);
1098 
1099  LOGF_DEBUG("CMD <%s>", cmd);
1100 
1101  tcflush(PortFD, TCIOFLUSH);
1102 
1103  if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1104  {
1105  tty_error_msg(rc, errstr, MAXRBUF);
1106  LOGF_ERROR("%s: %s.", __FUNCTION__, errstr);
1107  return false;
1108  }
1109 
1110  if ( (rc = tty_read_section(PortFD, res, '#', NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1111  {
1112  tty_error_msg(rc, errstr, MAXRBUF);
1113  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1114  return false;
1115  }
1116 
1117  res[nbytes_read - 1] = '\0';
1118 
1119  LOGF_DEBUG("RES <%s>", res);
1120 
1121  if (type == MOTOR_FOCUS)
1122  FocusStepDelayN[0].value = atoi(res);
1123  else if (type == MOTOR_ROTATOR)
1124  RotatorStepDelayN[0].value = atoi(res);
1125  else
1126  AuxStepDelayN[0].value = atoi(res);
1127 
1128  return true;
1129 }
1130 
1131 bool NightCrawler::setStepDelay(MotorType type, uint32_t delay)
1132 {
1133  char cmd[16] = {0};
1134  char res[16] = {0};
1135 
1136  int nbytes_written = 0, nbytes_read = 0, rc = -1;
1137  char errstr[MAXRBUF];
1138 
1139  snprintf(cmd, 16, "%dSR %03d#", type + 1, delay);
1140 
1141  LOGF_DEBUG("CMD <%s>", cmd);
1142 
1143  tcflush(PortFD, TCIOFLUSH);
1144 
1145  if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1146  {
1147  tty_error_msg(rc, errstr, MAXRBUF);
1148  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1149  return false;
1150  }
1151 
1152  if ( (rc = tty_read(PortFD, res, 1, NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1153  {
1154  tty_error_msg(rc, errstr, MAXRBUF);
1155  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1156  return false;
1157  }
1158 
1159  res[nbytes_read] = '\0';
1160 
1161  LOGF_DEBUG("RES <%s>", res);
1162 
1163  return (res[0] == '#');
1164 }
1165 
1166 bool NightCrawler::getLimitSwitchStatus()
1167 {
1168  char cmd[16] = "GS#";
1169  char res[16] = {0};
1170 
1171  int nbytes_written = 0, nbytes_read = 0, rc = -1;
1172  char errstr[MAXRBUF];
1173 
1174  LOGF_DEBUG("CMD <%s>", cmd);
1175 
1176  tcflush(PortFD, TCIOFLUSH);
1177 
1178  if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1179  {
1180  tty_error_msg(rc, errstr, MAXRBUF);
1181  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1182  return false;
1183  }
1184 
1185  if ( (rc = tty_read_section(PortFD, res, '#', NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1186  {
1187  tty_error_msg(rc, errstr, MAXRBUF);
1188  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1189  return false;
1190  }
1191 
1192  res[nbytes_read - 1] = '\0';
1193 
1194  LOGF_DEBUG("RES <%s>", res);
1195 
1196  int value = atoi(res);
1197 
1198  LimitSwitchL[ROTATION_SWITCH].s = (value & 0x01) ? IPS_ALERT : IPS_OK;
1199  LimitSwitchL[OUT_SWITCH].s = (value & 0x02) ? IPS_ALERT : IPS_OK;
1200  LimitSwitchL[IN_SWITCH].s = (value & 0x04) ? IPS_ALERT : IPS_OK;
1201 
1202  return true;
1203 }
1204 
1205 bool NightCrawler::findHome(uint8_t motorTypes)
1206 {
1207  char cmd[16] = {0};
1208  char res[16] = {0};
1209 
1210  int nbytes_written = 0, nbytes_read = 0, rc = -1;
1211  char errstr[MAXRBUF];
1212 
1213  snprintf(cmd, 16, "SH %02d#", motorTypes);
1214 
1215  LOGF_DEBUG("CMD <%s>", cmd);
1216 
1217  tcflush(PortFD, TCIOFLUSH);
1218 
1219  if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1220  {
1221  tty_error_msg(rc, errstr, MAXRBUF);
1222  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1223  return false;
1224  }
1225 
1226  if ( (rc = tty_read(PortFD, res, 1, NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1227  {
1228  tty_error_msg(rc, errstr, MAXRBUF);
1229  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1230  return false;
1231  }
1232 
1233  res[nbytes_read] = '\0';
1234 
1235  LOGF_DEBUG("RES <%s>", res);
1236 
1237  return (res[0] == '#');
1238 }
1239 
1240 bool NightCrawler::isHomingComplete()
1241 {
1242  char res[16] = {0};
1243  int nbytes_read = 0, rc = -1;
1244 
1245  if ( (rc = tty_read_section(PortFD, res, '#', NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1246  {
1247  // No error as we are waiting until controller returns "OK#"
1248  LOG_DEBUG("Waiting for NightCrawler to complete homing...");
1249  return false;
1250  }
1251 
1252  res[nbytes_read - 1] = '\0';
1253 
1254  LOGF_DEBUG("RES <%s>", res);
1255 
1256  return (strcmp("OK", res) == 0);
1257 }
1258 
1259 bool NightCrawler::setEncodersEnabled(bool enable)
1260 {
1261  char cmd[16] = {0};
1262  char res[16] = {0};
1263 
1264  int nbytes_written = 0, nbytes_read = 0, rc = -1;
1265  char errstr[MAXRBUF];
1266 
1267  snprintf(cmd, 16, "PE %s#", enable ? "01" : "00");
1268 
1269  LOGF_DEBUG("CMD <%s>", cmd);
1270 
1271  tcflush(PortFD, TCIOFLUSH);
1272 
1273  if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1274  {
1275  tty_error_msg(rc, errstr, MAXRBUF);
1276  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1277  return false;
1278  }
1279 
1280  if ( (rc = tty_read_section(PortFD, res, '#', NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1281  {
1282  tty_error_msg(rc, errstr, MAXRBUF);
1283  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1284  return false;
1285  }
1286 
1287  res[nbytes_read] = '\0';
1288 
1289  LOGF_DEBUG("RES <%s>", res);
1290 
1291  return true;
1292 }
1293 
1294 bool NightCrawler::setDisplayBrightness(uint8_t value)
1295 {
1296  char cmd[16] = {0};
1297  char res[16] = {0};
1298 
1299  int nbytes_written = 0, nbytes_read = 0, rc = -1;
1300  char errstr[MAXRBUF];
1301 
1302  snprintf(cmd, 16, "PD %03d#", value);
1303 
1304  LOGF_DEBUG("CMD <%s>", cmd);
1305 
1306  tcflush(PortFD, TCIOFLUSH);
1307 
1308  if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1309  {
1310  tty_error_msg(rc, errstr, MAXRBUF);
1311  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1312  return false;
1313  }
1314 
1315  if ( (rc = tty_read(PortFD, res, 1, NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1316  {
1317  tty_error_msg(rc, errstr, MAXRBUF);
1318  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1319  return false;
1320  }
1321 
1322  res[nbytes_read] = '\0';
1323 
1324  LOGF_DEBUG("RES <%s>", res);
1325 
1326  return (res[0] == '#');
1327 }
1328 
1329 bool NightCrawler::setSleepBrightness(uint8_t value)
1330 {
1331  char cmd[16] = {0};
1332  char res[16] = {0};
1333 
1334  int nbytes_written = 0, nbytes_read = 0, rc = -1;
1335  char errstr[MAXRBUF];
1336 
1337  snprintf(cmd, 16, "PL %03d#", value);
1338 
1339  LOGF_DEBUG("CMD <%s>", cmd);
1340 
1341  tcflush(PortFD, TCIOFLUSH);
1342 
1343  if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1344  {
1345  tty_error_msg(rc, errstr, MAXRBUF);
1346  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1347  return false;
1348  }
1349 
1350  if ( (rc = tty_read(PortFD, res, 1, NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1351  {
1352  tty_error_msg(rc, errstr, MAXRBUF);
1353  LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1354  return false;
1355  }
1356 
1357  res[nbytes_read] = '\0';
1358 
1359  LOGF_DEBUG("RES <%s>", res);
1360 
1361  return (res[0] == '#');
1362 }
1363 
1365 {
1366  Focuser::saveConfigItems(fp);
1367  RI::saveConfigItems(fp);
1368 
1369  IUSaveConfigNumber(fp, &BrightnessNP);
1370  IUSaveConfigNumber(fp, &FocusStepDelayNP);
1371  IUSaveConfigNumber(fp, &RotatorStepDelayNP);
1372  IUSaveConfigNumber(fp, &AuxStepDelayNP);
1373  CustomRotatorStepNP.save(fp);
1374 
1375  return true;
1376 }
1377 
1379 {
1380  if (findHome(0x02))
1381  {
1382  FindHomeSP.s = IPS_BUSY;
1383  FindHomeS[0].s = ISS_ON;
1384  IDSetSwitch(&FindHomeSP, nullptr);
1385  LOG_WARN("Homing process can take up to 10 minutes. You cannot control the unit until the process is fully complete.");
1386  return IPS_BUSY;
1387  }
1388  else
1389  {
1390  FindHomeSP.s = IPS_ALERT;
1391  FindHomeS[0].s = ISS_OFF;
1392  IDSetSwitch(&FindHomeSP, nullptr);
1393  LOG_ERROR("Failed to start homing process.");
1394  return IPS_ALERT;
1395  }
1396 }
1397 
1399 {
1400  // Rotator move 0 to +180 degrees CCW
1401  // Rotator move 0 to -180 degrees CW
1402  // This is from looking at rotator from behind.
1403  const bool isReversed = ReverseRotatorS[INDI_ENABLED].s == ISS_ON;
1404  auto newAngle = ( angle > 180 ? angle - 360 : angle);
1405  if (isReversed)
1406  newAngle *= -1;
1407 
1408  auto newTarget = newAngle * m_RotatorTicksPerDegree;
1409  if (newTarget < RotatorAbsPosN[0].min)
1410  newTarget = RotatorAbsPosN[0].min;
1411  else if (newTarget > RotatorAbsPosN[0].max)
1412  newTarget = RotatorAbsPosN[0].max;
1413 
1414  bool rc = gotoMotor(MOTOR_ROTATOR, static_cast<int32_t>(newTarget));
1415 
1416  if (rc)
1417  {
1418  RotatorAbsPosNP.s = IPS_BUSY;
1419  IDSetNumber(&RotatorAbsPosNP, nullptr);
1420  return IPS_BUSY;
1421  }
1422 
1423  return IPS_ALERT;
1424 }
1425 
1426 bool NightCrawler::SyncRotator(double angle)
1427 {
1428  const bool isReversed = ReverseRotatorS[INDI_ENABLED].s == ISS_ON;
1429  auto newAngle = ( angle > 180 ? angle - 360 : angle);
1430  if (isReversed)
1431  newAngle *= -1;
1432 
1433  auto newTarget = newAngle * m_RotatorTicksPerDegree;
1434  if (newTarget < RotatorAbsPosN[0].min)
1435  newTarget = RotatorAbsPosN[0].min;
1436  else if (newTarget > RotatorAbsPosN[0].max)
1437  newTarget = RotatorAbsPosN[0].max;
1438 
1439  return syncMotor(MOTOR_ROTATOR, static_cast<int32_t>(newTarget));
1440 }
1441 
1443 {
1444  bool rc = stopMotor(MOTOR_ROTATOR);
1445  if (rc && RotatorAbsPosNP.s != IPS_OK)
1446  {
1447  RotatorAbsPosNP.s = IPS_OK;
1448  IDSetNumber(&RotatorAbsPosNP, nullptr);
1449  }
1450 
1451  return rc;
1452 }
1453 
1455 {
1456  // Immediately update the angle after reverse is set.
1457  if (enabled)
1458  GotoRotatorN[0].value = range360(360 - (RotatorAbsPosN[0].value / m_RotatorTicksPerDegree));
1459  else
1460  GotoRotatorN[0].value = range360(RotatorAbsPosN[0].value / m_RotatorTicksPerDegree);
1461  IDSetNumber(&GotoRotatorNP, nullptr);
1462  return true;
1463 }
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
bool isConnected() const
Definition: basedevice.cpp:520
const char * getDeviceName() const
Definition: basedevice.cpp:821
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual bool Disconnect()
Disconnect from device.
void setDefaultPollingPeriod(uint32_t msec)
setDefaultPollingPeriod Change the default polling period to call TimerHit() function in the driver.
virtual void setConnected(bool status, IPState state=IPS_OK, const char *msg=nullptr)
Set connection switch status in the client.
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.
virtual bool Connect()
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
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 setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void save(FILE *f) const
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 processSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process Rotator switch properties.
bool saveConfigItems(FILE *fp)
saveConfigItems save focuser properties defined in the interface in config file
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.
ISwitchVectorProperty HomeRotatorSP
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process Rotator number properties.
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual IPState MoveRotator(double angle) override
MoveRotator Go to specific angle.
const char * getDefaultName() override
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool AbortRotator() override
AbortRotator Abort all motion.
virtual bool AbortFocuser() override
AbortFocuser all focus motion.
virtual IPState HomeRotator() override
HomeRotator Go to home position.
virtual IPState MoveAbsFocuser(uint32_t targetTicks) override
MoveFocuser the focuser to an absolute position.
virtual bool Handshake() override
perform handshake with device to check communication
static void abnormalDisconnectCallback(void *userpointer)
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool SyncRotator(double angle) override
SyncRotator Set current angle as the supplied angle without moving the rotator.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the Device Port and Focuser Presets in the configuration file
virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveFocuser the focuser to an relative position.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool ReverseRotator(bool enabled) override
ReverseRotator Reverse the direction of the rotator. CW is usually the normal direction,...
virtual bool initProperties() override
Initilize properties initial state and value. The child class must implement this function.
Provides interface to implement Rotator functionality.
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
int IEAddTimer(int millisecs, IE_TCF *fp, void *p)
Register a new single-shot timer function, fp, to be called with ud as argument after ms.
Definition: eventloop.c:582
double max(void)
double min(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
@ IP_WO
Definition: indiapi.h:185
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
@ ISR_NOFMANY
Definition: indiapi.h:175
@ ISR_ATMOST1
Definition: indiapi.h:174
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
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition: indicom.c:482
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 IUFillLight(ILight *lp, const char *name, const char *label, IPState s)
Assign attributes for a light property. The light's auxiliary elements will be set to NULL.
Definition: indidevapi.c:169
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
void IUFillLightVector(ILightVectorProperty *lvp, ILight *lp, int nlp, const char *dev, const char *name, const char *label, const char *group, IPState s)
Assign attributes for a light vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:255
void IUSaveConfigNumber(FILE *fp, const INumberVectorProperty *nvp)
Add a number vector property value to the configuration file.
Definition: indidevapi.c:15
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 IDSetLight(const ILightVectorProperty *lvp, const char *fmt,...)
Definition: indidriver.c:1251
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
Definition: indidriver.c:1211
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
int IUUpdateNumber(INumberVectorProperty *nvp, double values[], char *names[], int n)
Update all numbers in a number vector property.
Definition: indidriver.c:1362
void IUUpdateMinMax(const INumberVectorProperty *nvp)
Function to update the min and max elements of a number in the client.
Definition: indidriver.c:1296
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOG_DEBUG(txt)
Definition: indilogger.h:75
#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
@ value
the parser finished reading a JSON value
#define NC_30_STEPS
#define NC_35_STEPS
#define AUX_TAB
#define ROTATOR_TAB
#define NIGHTCRAWLER_TIMEOUT
#define NIGHTCRAWLER_THRESHOLD
#define NC_25_STEPS
#define SETTINGS_TAB
__le16 type
Definition: pwc-ioctl.h:0
__u8 cmd[4]
Definition: pwc-ioctl.h:2
char name[MAXINDINAME]
Definition: indiapi.h:421
char name[MAXINDINAME]
Definition: indiapi.h:323
char name[MAXINDINAME]
Definition: indiapi.h:371