Instrument Neutral Distributed Interface INDI 2.2.5
nightcrawler.cpp
Go to the documentation of this file.
1/*
2 NightCrawler NightCrawler Focuser & Rotator
3 Copyright (C) 2017 Jasem Mutlaq ([email protected])
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.
45static 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 FocusSpeedNP[0].setMin(1);
61 FocusSpeedNP[0].setMax(1);
62 FocusSpeedNP[0].setValue(1);
63
64 // Focus Sync
65 SyncFocusNP[0].fill("FOCUS_SYNC_OFFSET", "Ticks", "%.f", 0, 100000., 0., 0.);
66 SyncFocusNP.fill(getDeviceName(), "FOCUS_SYNC", "Sync", MAIN_CONTROL_TAB, IP_RW, 0,
67 IPS_IDLE );
68
69 // Voltage
70 VoltageNP[0].fill("VALUE", "Value (v)", "%.2f", 0, 30., 1., 0.);
71 VoltageNP.fill(getDeviceName(), "Voltage", "Voltage", MAIN_CONTROL_TAB, IP_RO, 0, IPS_IDLE );
72
73 // Temperature
74 TemperatureNP[0].fill("TEMPERATURE", "Value (C)", "%.2f", -100, 100., 1., 0.);
75 TemperatureNP.fill(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 FocusStepDelayNP[0].fill("FOCUS_STEP", "Value", "%.f", 7, 100., 1., 7.);
85 FocusStepDelayNP.fill(getDeviceName(), "FOCUS_STEP_DELAY", "Step Rate", SETTINGS_TAB,
86 IP_RW, 0, IPS_IDLE );
87
88 // Limit Switch
89 LimitSwitchLP[ROTATION_SWITCH].fill("ROTATION_SWITCH", "Rotation Home", IPS_OK);
90 LimitSwitchLP[OUT_SWITCH].fill("OUT_SWITCH", "Focus Out Limit", IPS_OK);
91 LimitSwitchLP[IN_SWITCH].fill("IN_SWITCH", "Focus In Limit", IPS_OK);
92 LimitSwitchLP.fill(getDeviceName(), "LIMIT_SWITCHES", "Limit Switch", SETTINGS_TAB,
93 IPS_IDLE);
94
95 // Home selection
96 HomeSelectionSP[MOTOR_FOCUS].fill("FOCUS", "Focuser", ISS_ON);
97 HomeSelectionSP[MOTOR_ROTATOR].fill( "ROTATOR", "Rotator", ISS_ON);
98 HomeSelectionSP[MOTOR_AUX].fill("AUX", "Aux", ISS_OFF);
99 HomeSelectionSP.fill(getDeviceName(), "HOME_SELECTION", "Home Select", SETTINGS_TAB,
101
102 // Home Find
103 FindHomeSP[0].fill("FIND", "Start", ISS_OFF);
104 FindHomeSP.fill(getDeviceName(), "FIND_HOME", "Home Find", SETTINGS_TAB, IP_RW, ISR_1OFMANY,
105 0, IPS_IDLE);
106
107 // Encoders
108 EncoderSP[INDI_ENABLED].fill("INDI_ENABLED", "Enabled", ISS_ON);
109 EncoderSP[INDI_DISABLED].fill("INDI_DISABLED", "Disabled", ISS_OFF);
110 EncoderSP.fill(getDeviceName(), "ENCODERS", "Encoders", SETTINGS_TAB, IP_RW, ISR_1OFMANY, 0,
111 IPS_IDLE);
112
113 // Brightness
114 BrightnessNP[BRIGHTNESS_DISPLAY].fill("BRIGHTNESS_DISPLAY", "Display", "%.f", 0, 255., 10., 150.);
115 BrightnessNP[BRIGHTNESS_SLEEP].fill("BRIGHTNESS_SLEEP", "Sleep", "%.f", 1, 255., 10., 16.);
116 BrightnessNP.fill(getDeviceName(), "BRIGHTNESS", "Brightness", SETTINGS_TAB, IP_RW, 0,
117 IPS_IDLE );
118
120 // Rotator Properties
122
124
125 // Rotator Ticks
126 RotatorAbsPosNP[0].fill("ROTATOR_ABSOLUTE_POSITION", "Ticks", "%.f", 0., 100000., 1000., 0.);
127 RotatorAbsPosNP.fill(getDeviceName(), "ABS_ROTATOR_POSITION", "Goto", ROTATOR_TAB, IP_RW,
128 0, IPS_IDLE );
129
130 // Rotator Step Delay
131 RotatorStepDelayNP[0].fill("ROTATOR_STEP", "Value", "%.f", 7, 100., 1., 7.);
132 RotatorStepDelayNP.fill(getDeviceName(), "ROTATOR_STEP_DELAY", "Step Rate",
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 GotoAuxNP[0].fill("AUX_ABSOLUTE_POSITION", "Ticks", "%.f", 0, 100000., 0., 0.);
145 GotoAuxNP.fill(getDeviceName(), "ABS_AUX_POSITION", "Goto", AUX_TAB, IP_RW, 0, IPS_IDLE );
146
147 // Abort Aux
148 AbortAuxSP[0].fill("ABORT", "Abort", ISS_OFF);
149 AbortAuxSP.fill(getDeviceName(), "AUX_ABORT_MOTION", "Abort Motion", AUX_TAB, IP_RW,
151
152 // Aux Sync
153 SyncAuxNP[0].fill("AUX_SYNC_TICK", "Ticks", "%.f", 0, 100000., 0., 0.);
154 SyncAuxNP.fill(getDeviceName(), "SYNC_AUX", "Sync", AUX_TAB, IP_RW, 0, IPS_IDLE );
155
156 // Aux Step Delay
157 AuxStepDelayNP[0].fill( "AUX_STEP", "Value", "%.f", 7, 100., 1., 7.);
158 AuxStepDelayNP.fill(getDeviceName(), "AUX_STEP_DELAY", "Step Rate", AUX_TAB, IP_RW, 0,
159 IPS_IDLE );
160
161 /* Relative and absolte movement */
162 FocusRelPosNP[0].setMin(0.);
163 FocusRelPosNP[0].setMax(50000.);
164 FocusRelPosNP[0].setValue(0);
165 FocusRelPosNP[0].setStep(1000);
166
167 FocusAbsPosNP[0].setMin(0.);
168 FocusAbsPosNP[0].setMax(100000.);
169 FocusAbsPosNP[0].setValue(0);
170 FocusAbsPosNP[0].setStep(1000);
171
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);
217 deleteProperty(VoltageNP);
218 deleteProperty(TemperatureNP);
219 deleteProperty(TemperatureOffsetNP.name);
220 deleteProperty(FocusStepDelayNP);
221 deleteProperty(LimitSwitchLP);
222 deleteProperty(EncoderSP);
223 deleteProperty(BrightnessNP);
224 deleteProperty(FindHomeSP);
225 deleteProperty(HomeSelectionSP);
226
227 // Rotator
229 deleteProperty(RotatorAbsPosNP);
230 deleteProperty(RotatorStepDelayNP);
231 deleteProperty(CustomRotatorStepNP);
232
233 // Aux
234 deleteProperty(GotoAuxNP);
235 deleteProperty(AbortAuxSP);
236 deleteProperty(SyncAuxNP);
237 deleteProperty(AuxStepDelayNP);
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
257bool NightCrawler::Ack()
258{
259 bool rcFirmware = getFirmware();
260 bool rcType = getFocuserType();
261
262 return (rcFirmware && rcType);
263}
264
265bool 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
296bool 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 RotatorAbsPosNP[0].setMin(-NC_25_STEPS / 2.0);
327 RotatorAbsPosNP[0].setMax(NC_25_STEPS / 2.0);
328 m_RotatorStepsPerRevolution = NC_25_STEPS;
329 }
330 else if (strcmp(resp, "3.0 NC") == 0)
331 {
332 RotatorAbsPosNP[0].setMin(-NC_30_STEPS / 2.0);
333 RotatorAbsPosNP[0].setMax(NC_30_STEPS / 2.0);
334 m_RotatorStepsPerRevolution = NC_30_STEPS;
335 }
336 else
337 {
338 RotatorAbsPosNP[0].setMin(-NC_35_STEPS / 2.0);
339 RotatorAbsPosNP[0].setMax(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
348bool 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
381bool 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 FocusAbsPosNP[0].setValue(position);
422 else if (type == MOTOR_ROTATOR)
423 RotatorAbsPosNP[0].setValue(position);
424 else
425 GotoAuxNP[0].setValue(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
444void 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
458bool NightCrawler::ISNewSwitch (const char * dev, const char * name, ISState * states, char * names[], int n)
459{
460 if(strcmp(dev, getDeviceName()) == 0)
461 {
462 if (HomeSelectionSP.isNameMatch(name))
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.setState(IPS_ALERT);
478 LOG_ERROR("At least one selection must be on.");
479 HomeSelectionSP.apply();
480 return false;
481 }
482
483 HomeSelectionSP.update(states, names, n);
484 HomeSelectionSP.setState(IPS_OK);
485 HomeSelectionSP.apply();
486 return true;
487 }
488 else if (FindHomeSP.isNameMatch(name))
489 {
490 uint8_t selection = 0;
491
492 if (HomeSelectionSP[MOTOR_FOCUS].getState() == ISS_ON)
493 selection |= 0x01;
494 if (HomeSelectionSP[MOTOR_ROTATOR].getState() == ISS_ON)
495 selection |= 0x02;
496 if (HomeSelectionSP[MOTOR_AUX].getState() == ISS_ON)
497 selection |= 0x04;
498
499 if (findHome(selection))
500 {
501 FindHomeSP.setState(IPS_BUSY);
502 FindHomeSP[0].setState(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.setState(IPS_ALERT);
508 FindHomeSP[0].setState(ISS_OFF);
509 LOG_ERROR("Failed to start homing process.");
510 }
511
512 FindHomeSP.apply();
513 return true;
514 }
515 else if (EncoderSP.isNameMatch(name))
516 {
517 EncoderSP.update(states, names, n);
518 EncoderSP.setState(setEncodersEnabled(EncoderSP[0].getState() == ISS_ON) ? IPS_OK : IPS_ALERT);
519 if (EncoderSP.getState() == IPS_OK)
520 LOGF_INFO("Encoders are %s", (EncoderSP[0].getState() == ISS_ON) ? "ON" : "OFF");
521 EncoderSP.apply();
522 return true;
523 }
524 else if (AbortAuxSP.isNameMatch(name))
525 {
526 AbortAuxSP.setState(stopMotor(MOTOR_AUX) ? IPS_OK : IPS_ALERT);
527 AbortAuxSP.apply();
528 if (AbortAuxSP.getState() == IPS_OK)
529 {
530 if (GotoAuxNP.getState() != IPS_OK)
531 {
532 GotoAuxNP.setState(IPS_OK);
533 GotoAuxNP.apply();
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
548bool 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 (SyncFocusNP.isNameMatch(name))
553 {
554 bool rc = syncMotor(MOTOR_FOCUS, static_cast<uint32_t>(values[0]));
555 SyncFocusNP.setState(rc ? IPS_OK : IPS_ALERT);
556 if (rc)
557 SyncFocusNP[0].setValue(values[0]);
558
559 SyncFocusNP.apply();
560 return true;
561 }
562 else if (SyncAuxNP.isNameMatch(name))
563 {
564 bool rc = syncMotor(MOTOR_AUX, static_cast<uint32_t>(values[0]));
565 SyncAuxNP.setState(rc ? IPS_OK : IPS_ALERT);
566 if (rc)
567 SyncAuxNP[0].setValue(values[0]);
568
569 SyncAuxNP.apply();
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 (FocusStepDelayNP.isNameMatch(name))
580 {
581 bool rc = setStepDelay(MOTOR_FOCUS, static_cast<uint32_t>(values[0]));
582 FocusStepDelayNP.setState(rc ? IPS_OK : IPS_ALERT);
583 if (rc)
584 FocusStepDelayNP[0].setValue(values[0]);
585 FocusStepDelayNP.apply();
586 return true;
587 }
588 else if (RotatorStepDelayNP.isNameMatch(name))
589 {
590 bool rc = setStepDelay(MOTOR_ROTATOR, static_cast<uint32_t>(values[0]));
591 RotatorStepDelayNP.setState(rc ? IPS_OK : IPS_ALERT);
592 if (rc)
593 RotatorStepDelayNP[0].setValue(values[0]);
594 RotatorStepDelayNP.apply();
595 return true;
596 }
597 else if (AuxStepDelayNP.isNameMatch(name))
598 {
599 bool rc = setStepDelay(MOTOR_AUX, static_cast<uint32_t>(values[0]));
600 AuxStepDelayNP.setState(rc ? IPS_OK : IPS_ALERT);
601 if (rc)
602 AuxStepDelayNP[0].setValue(values[0]);
603 AuxStepDelayNP.apply();
604 return true;
605 }
606 else if (BrightnessNP.isNameMatch(name))
607 {
608 BrightnessNP.update(values, names, n);
609 bool rcDisplay = setDisplayBrightness(static_cast<uint8_t>(BrightnessNP[BRIGHTNESS_DISPLAY].getValue()));
610 bool rcSleep = setSleepBrightness(static_cast<uint8_t>(BrightnessNP[BRIGHTNESS_SLEEP].getValue()));
611 if (rcDisplay && rcSleep)
612 BrightnessNP.setState(IPS_OK);
613 else
614 BrightnessNP.setState(IPS_ALERT);
615
616 BrightnessNP.apply();
617 return true;
618 }
619 else if (GotoAuxNP.isNameMatch(name))
620 {
621 bool rc = gotoMotor(MOTOR_AUX, static_cast<int32_t>(values[0]));
622 GotoAuxNP.setState(rc ? IPS_BUSY : IPS_OK);
623 GotoAuxNP.apply();
624 LOGF_INFO("Aux moving to %.f...", values[0]);
625 return true;
626 }
627 else if (RotatorAbsPosNP.isNameMatch(name))
628 {
629 RotatorAbsPosNP.setState(gotoMotor(MOTOR_ROTATOR, static_cast<int32_t>(values[0])) ? IPS_BUSY : IPS_ALERT);
630 RotatorAbsPosNP.apply();
631 if (RotatorAbsPosNP.getState() == 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 RotatorAbsPosNP[0].setMin(customValue / 2.0);
645 RotatorAbsPosNP[0].setMax(customValue / 2.0);
646 m_RotatorStepsPerRevolution = customValue;
647 m_RotatorTicksPerDegree = m_RotatorStepsPerRevolution / 360.0;
648 RotatorAbsPosNP.updateMinMax();
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 = FocusAbsPosNP[0].getValue() - ticks;
689 else
690 newPosition = FocusAbsPosNP[0].getValue() + ticks;
691
692 rc = gotoMotor(MOTOR_FOCUS, newPosition);
693
694 if (!rc)
695 return IPS_ALERT;
696
697 FocusRelPosNP[0].setValue(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.getState() == IPS_BUSY || HomeRotatorSP.getState() == IPS_BUSY)
715 {
716 if (isHomingComplete())
717 {
721
722 FindHomeSP[0].setState(ISS_OFF);
723 FindHomeSP.setState(IPS_OK);
724 FindHomeSP.apply();
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(TemperatureNP[0].getValue() - lastTemperature) > NIGHTCRAWLER_THRESHOLD)
736 {
737 lastTemperature = TemperatureNP[0].getValue();
738 TemperatureNP.apply();
739 }
740
741 // #3 Get Voltage
742 rc = getVoltage();
743 if (rc && std::abs(VoltageNP[0].getValue() - lastVoltage) > NIGHTCRAWLER_THRESHOLD)
744 {
745 lastVoltage = VoltageNP[0].getValue();
746 VoltageNP.apply();
747 }
748
749 // #4 Get Limit Switch Status
750 rc = getLimitSwitchStatus();
751 if (rc && (LimitSwitchLP[ROTATION_SWITCH].getState() != rotationLimit
752 || LimitSwitchLP[OUT_SWITCH].getState() != outSwitchLimit
753 || LimitSwitchLP[IN_SWITCH].getState() != inSwitchLimit))
754 {
755 rotationLimit = LimitSwitchLP[ROTATION_SWITCH].getState();
756 outSwitchLimit = LimitSwitchLP[OUT_SWITCH].getState();
757 inSwitchLimit = LimitSwitchLP[IN_SWITCH].getState();
758 LimitSwitchLP.apply();
759 }
760
761 // #5 Focus Position & Status
762 bool absFocusUpdated = false;
763
765 {
766 // Stopped moving
767 if (!isMotorMoving(MOTOR_FOCUS))
768 {
771 {
774 }
775 absFocusUpdated = true;
776 }
777 }
778 rc = getPosition(MOTOR_FOCUS);
779 if (rc && std::abs(FocusAbsPosNP[0].getValue() - lastFocuserPosition) > NIGHTCRAWLER_THRESHOLD)
780 {
781 lastFocuserPosition = FocusAbsPosNP[0].getValue();
782 absFocusUpdated = true;
783 }
784 if (absFocusUpdated)
786
787 // #6 Rotator Position & Status
788 bool absRotatorUpdated = false;
789
790 if (RotatorAbsPosNP.getState() == IPS_BUSY)
791 {
792 // Stopped moving
793 if (!isMotorMoving(MOTOR_ROTATOR))
794 {
795 RotatorAbsPosNP.setState(IPS_OK);
797 absRotatorUpdated = true;
798 LOG_INFO("Rotator motion complete.");
799 }
800 }
801 rc = getPosition(MOTOR_ROTATOR);
802 // If absolute position is zero, we must sync to 180 degrees so we can rotate in both directions freely.
803 // Also sometimes Rotator motor returns negative result, we must sync it to 180 degrees as well.
804 while (RotatorAbsPosNP[0].getValue() < -m_RotatorStepsPerRevolution
805 || RotatorAbsPosNP[0].getValue() > m_RotatorStepsPerRevolution)
806 {
807 // Update value to take care of multiple rotations.
808 const auto newOffset = static_cast<int32_t>(RotatorAbsPosNP[0].getValue()) % m_RotatorStepsPerRevolution;
809 LOGF_INFO("Out of bounds value detected. Syncing rotator position to %d", newOffset);
810 syncMotor(MOTOR_ROTATOR, newOffset);
811 rc = getPosition(MOTOR_ROTATOR);
812 }
813 if (rc && std::abs(RotatorAbsPosNP[0].getValue() - lastRotatorPosition) > NIGHTCRAWLER_THRESHOLD)
814 {
815 lastRotatorPosition = RotatorAbsPosNP[0].getValue();
816 if (ReverseRotatorSP[INDI_ENABLED].getState() == ISS_ON)
817 GotoRotatorNP[0].setValue(range360(360 - (RotatorAbsPosNP[0].getValue() / m_RotatorTicksPerDegree)));
818 else
819 GotoRotatorNP[0].setValue(range360(RotatorAbsPosNP[0].getValue() / m_RotatorTicksPerDegree));
820 absRotatorUpdated = true;
821 }
822 if (absRotatorUpdated)
823 {
824 RotatorAbsPosNP.apply();
826 }
827
828 // #7 Aux Position & Status
829 bool absAuxUpdated = false;
830
831 if (GotoAuxNP.getState() == IPS_BUSY)
832 {
833 // Stopped moving
834 if (!isMotorMoving(MOTOR_AUX))
835 {
836 GotoAuxNP.setState(IPS_OK);
837 absAuxUpdated = true;
838 LOG_INFO("Aux motion complete.");
839 }
840 }
841 rc = getPosition(MOTOR_AUX);
842 if (rc && std::abs(GotoAuxNP[0].getValue() - lastAuxPosition) > NIGHTCRAWLER_THRESHOLD)
843 {
844 lastAuxPosition = GotoAuxNP[0].getValue();
845 absAuxUpdated = true;
846 }
847 if (absAuxUpdated)
848 GotoAuxNP.apply();
849
851}
852
854{
855 return stopMotor(MOTOR_FOCUS);
856}
857
858bool NightCrawler::syncMotor(MotorType type, uint32_t position)
859{
860 char cmd[16] = {0};
861 char res[16] = {0};
862
863 int nbytes_written = 0, nbytes_read = 0, rc = -1;
864 char errstr[MAXRBUF];
865
866 snprintf(cmd, 16, "%dSP %d#", type + 1, position);
867
868 LOGF_DEBUG("CMD <%s>", cmd);
869
870 tcflush(PortFD, TCIOFLUSH);
871
872 if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
873 {
874 tty_error_msg(rc, errstr, MAXRBUF);
875 LOGF_ERROR("%s: %s.", __FUNCTION__, errstr);
876 return false;
877 }
878
879 if ( (rc = tty_read(PortFD, res, 1, NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
880 {
881 tty_error_msg(rc, errstr, MAXRBUF);
882 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
883 return false;
884 }
885
886 res[nbytes_read] = '\0';
887
888 LOGF_DEBUG("RES <%s>", res);
889
890 return (res[0] == '#');
891}
892
893bool NightCrawler::startMotor(MotorType type)
894{
895 char cmd[16] = {0};
896 char res[16] = {0};
897
898 int nbytes_written = 0, nbytes_read = 0, rc = -1;
899 char errstr[MAXRBUF];
900
901 snprintf(cmd, 16, "%dSM#", type + 1);
902
903 LOGF_DEBUG("CMD <%s>", cmd);
904
905 tcflush(PortFD, TCIOFLUSH);
906
907 if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
908 {
909 tty_error_msg(rc, errstr, MAXRBUF);
910 LOGF_ERROR("%s: %s.", __FUNCTION__, errstr);
911 return false;
912 }
913
914 if ( (rc = tty_read(PortFD, res, 1, NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
915 {
916 tty_error_msg(rc, errstr, MAXRBUF);
917 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
918 return false;
919 }
920
921 res[nbytes_read] = '\0';
922
923 LOGF_DEBUG("RES <%s>", res);
924
925 return (res[0] == '#');
926}
927
928bool NightCrawler::stopMotor(MotorType type)
929{
930 char cmd[16] = {0};
931 char res[16] = {0};
932
933 int nbytes_written = 0, nbytes_read = 0, rc = -1;
934 char errstr[MAXRBUF];
935
936 snprintf(cmd, 16, "%dSQ#", type + 1);
937
938 LOGF_DEBUG("CMD <%s>", cmd);
939
940 tcflush(PortFD, TCIOFLUSH);
941
942 if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
943 {
944 tty_error_msg(rc, errstr, MAXRBUF);
945 LOGF_ERROR("%s: %s.", __FUNCTION__, errstr);
946 return false;
947 }
948
949 if ( (rc = tty_read(PortFD, res, 1, NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
950 {
951 tty_error_msg(rc, errstr, MAXRBUF);
952 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
953 return false;
954 }
955
956 res[nbytes_read] = '\0';
957
958 LOGF_DEBUG("RES <%s>", res);
959
960 return (res[0] == '#');
961}
962
963bool NightCrawler::isMotorMoving(MotorType type)
964{
965 char cmd[16] = {0};
966 char res[16] = {0};
967
968 int nbytes_written = 0, nbytes_read = 0, rc = -1;
969 char errstr[MAXRBUF];
970
971 snprintf(cmd, 16, "%dGM#", type + 1);
972
973 LOGF_DEBUG("CMD <%s>", cmd);
974
975 tcflush(PortFD, TCIOFLUSH);
976
977 if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
978 {
979 tty_error_msg(rc, errstr, MAXRBUF);
980 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
981 return false;
982 }
983
984 if ( (rc = tty_read_section(PortFD, res, '#', NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
985 {
986 tty_error_msg(rc, errstr, MAXRBUF);
987 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
988 return false;
989 }
990
991 res[nbytes_read - 1] = '\0';
992
993 LOGF_DEBUG("RES <%s>", res);
994
995 return (strcmp("01", res) == 0);
996}
997
998bool NightCrawler::getTemperature()
999{
1000 char cmd[16] = "GT#";
1001 char res[16] = {0};
1002
1003 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1004 char errstr[MAXRBUF];
1005
1006 LOGF_DEBUG("CMD <%s>", cmd);
1007
1008 tcflush(PortFD, TCIOFLUSH);
1009
1010 if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1011 {
1012 tty_error_msg(rc, errstr, MAXRBUF);
1013 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1014 return false;
1015 }
1016
1017 if ( (rc = tty_read_section(PortFD, res, '#', NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1018 {
1019 tty_error_msg(rc, errstr, MAXRBUF);
1020 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1021 return false;
1022 }
1023
1024 res[nbytes_read - 1] = '\0';
1025
1026 LOGF_DEBUG("RES <%s>", res);
1027
1028 TemperatureNP[0].setValue(atoi(res) / 10.0);
1029
1030 return true;
1031}
1032
1033bool NightCrawler::getVoltage()
1034{
1035 char cmd[16] = "GV#";
1036 char res[16] = {0};
1037
1038 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1039 char errstr[MAXRBUF];
1040
1041 LOGF_DEBUG("CMD <%s>", cmd);
1042
1043 tcflush(PortFD, TCIOFLUSH);
1044
1045 if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1046 {
1047 tty_error_msg(rc, errstr, MAXRBUF);
1048 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1049 return false;
1050 }
1051
1052 if ( (rc = tty_read_section(PortFD, res, '#', NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1053 {
1054 tty_error_msg(rc, errstr, MAXRBUF);
1055 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1056 return false;
1057 }
1058
1059 res[nbytes_read - 1] = '\0';
1060
1061 LOGF_DEBUG("RES <%s>", res);
1062
1063 VoltageNP[0].setValue(atoi(res) / 10.0);
1064
1065 return true;
1066}
1067
1068bool NightCrawler::setTemperatureOffset(double offset)
1069{
1070 char cmd[16] = {0};
1071
1072 int nbytes_written = 0, rc = -1;
1073 char errstr[MAXRBUF];
1074
1075 snprintf(cmd, 16, "Pt %03d#", static_cast<int>(offset * 10));
1076
1077 LOGF_DEBUG("CMD <%s>", cmd);
1078
1079 tcflush(PortFD, TCIOFLUSH);
1080
1081 if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1082 {
1083 tty_error_msg(rc, errstr, MAXRBUF);
1084 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1085 return false;
1086 }
1087
1088 return true;
1089}
1090
1091bool NightCrawler::getStepDelay(MotorType type)
1092{
1093 char cmd[16] = {0};
1094 char res[16] = {0};
1095
1096 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1097 char errstr[MAXRBUF];
1098
1099 snprintf(cmd, 16, "%dSR#", type + 1);
1100
1101 LOGF_DEBUG("CMD <%s>", cmd);
1102
1103 tcflush(PortFD, TCIOFLUSH);
1104
1105 if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1106 {
1107 tty_error_msg(rc, errstr, MAXRBUF);
1108 LOGF_ERROR("%s: %s.", __FUNCTION__, errstr);
1109 return false;
1110 }
1111
1112 if ( (rc = tty_read_section(PortFD, res, '#', NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1113 {
1114 tty_error_msg(rc, errstr, MAXRBUF);
1115 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1116 return false;
1117 }
1118
1119 res[nbytes_read - 1] = '\0';
1120
1121 LOGF_DEBUG("RES <%s>", res);
1122
1123 if (type == MOTOR_FOCUS)
1124 FocusStepDelayNP[0].setValue(atoi(res));
1125 else if (type == MOTOR_ROTATOR)
1126 RotatorStepDelayNP[0].setValue(atoi(res));
1127 else
1128 AuxStepDelayNP[0].setValue(atoi(res));
1129
1130 return true;
1131}
1132
1133bool NightCrawler::setStepDelay(MotorType type, uint32_t delay)
1134{
1135 char cmd[16] = {0};
1136 char res[16] = {0};
1137
1138 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1139 char errstr[MAXRBUF];
1140
1141 snprintf(cmd, 16, "%dSR %03d#", type + 1, delay);
1142
1143 LOGF_DEBUG("CMD <%s>", cmd);
1144
1145 tcflush(PortFD, TCIOFLUSH);
1146
1147 if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1148 {
1149 tty_error_msg(rc, errstr, MAXRBUF);
1150 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1151 return false;
1152 }
1153
1154 if ( (rc = tty_read(PortFD, res, 1, NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1155 {
1156 tty_error_msg(rc, errstr, MAXRBUF);
1157 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1158 return false;
1159 }
1160
1161 res[nbytes_read] = '\0';
1162
1163 LOGF_DEBUG("RES <%s>", res);
1164
1165 return (res[0] == '#');
1166}
1167
1168bool NightCrawler::getLimitSwitchStatus()
1169{
1170 char cmd[16] = "GS#";
1171 char res[16] = {0};
1172
1173 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1174 char errstr[MAXRBUF];
1175
1176 LOGF_DEBUG("CMD <%s>", cmd);
1177
1178 tcflush(PortFD, TCIOFLUSH);
1179
1180 if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1181 {
1182 tty_error_msg(rc, errstr, MAXRBUF);
1183 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1184 return false;
1185 }
1186
1187 if ( (rc = tty_read_section(PortFD, res, '#', NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1188 {
1189 tty_error_msg(rc, errstr, MAXRBUF);
1190 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1191 return false;
1192 }
1193
1194 res[nbytes_read - 1] = '\0';
1195
1196 LOGF_DEBUG("RES <%s>", res);
1197
1198 int value = atoi(res);
1199
1200 LimitSwitchLP[ROTATION_SWITCH].setState((value & 0x01) ? IPS_ALERT : IPS_OK);
1201 LimitSwitchLP[OUT_SWITCH].setState((value & 0x02) ? IPS_ALERT : IPS_OK);
1202 LimitSwitchLP[IN_SWITCH].setState((value & 0x04) ? IPS_ALERT : IPS_OK);
1203
1204 return true;
1205}
1206
1207bool NightCrawler::findHome(uint8_t motorTypes)
1208{
1209 char cmd[16] = {0};
1210 char res[16] = {0};
1211
1212 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1213 char errstr[MAXRBUF];
1214
1215 snprintf(cmd, 16, "SH %02d#", motorTypes);
1216
1217 LOGF_DEBUG("CMD <%s>", cmd);
1218
1219 tcflush(PortFD, TCIOFLUSH);
1220
1221 if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1222 {
1223 tty_error_msg(rc, errstr, MAXRBUF);
1224 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1225 return false;
1226 }
1227
1228 if ( (rc = tty_read(PortFD, res, 1, NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1229 {
1230 tty_error_msg(rc, errstr, MAXRBUF);
1231 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1232 return false;
1233 }
1234
1235 res[nbytes_read] = '\0';
1236
1237 LOGF_DEBUG("RES <%s>", res);
1238
1239 return (res[0] == '#');
1240}
1241
1242bool NightCrawler::isHomingComplete()
1243{
1244 char res[16] = {0};
1245 int nbytes_read = 0, rc = -1;
1246
1247 if ( (rc = tty_read_section(PortFD, res, '#', NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1248 {
1249 // No error as we are waiting until controller returns "OK#"
1250 LOG_DEBUG("Waiting for NightCrawler to complete homing...");
1251 return false;
1252 }
1253
1254 res[nbytes_read - 1] = '\0';
1255
1256 LOGF_DEBUG("RES <%s>", res);
1257
1258 return (strcmp("OK", res) == 0);
1259}
1260
1261bool NightCrawler::setEncodersEnabled(bool enable)
1262{
1263 char cmd[16] = {0};
1264 char res[16] = {0};
1265
1266 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1267 char errstr[MAXRBUF];
1268
1269 snprintf(cmd, 16, "PE %s#", enable ? "01" : "00");
1270
1271 LOGF_DEBUG("CMD <%s>", cmd);
1272
1273 tcflush(PortFD, TCIOFLUSH);
1274
1275 if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1276 {
1277 tty_error_msg(rc, errstr, MAXRBUF);
1278 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1279 return false;
1280 }
1281
1282 if ( (rc = tty_read_section(PortFD, res, '#', NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1283 {
1284 tty_error_msg(rc, errstr, MAXRBUF);
1285 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1286 return false;
1287 }
1288
1289 res[nbytes_read] = '\0';
1290
1291 LOGF_DEBUG("RES <%s>", res);
1292
1293 return true;
1294}
1295
1296bool NightCrawler::setDisplayBrightness(uint8_t value)
1297{
1298 char cmd[16] = {0};
1299 char res[16] = {0};
1300
1301 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1302 char errstr[MAXRBUF];
1303
1304 snprintf(cmd, 16, "PD %03d#", value);
1305
1306 LOGF_DEBUG("CMD <%s>", cmd);
1307
1308 tcflush(PortFD, TCIOFLUSH);
1309
1310 if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1311 {
1312 tty_error_msg(rc, errstr, MAXRBUF);
1313 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1314 return false;
1315 }
1316
1317 if ( (rc = tty_read(PortFD, res, 1, NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1318 {
1319 tty_error_msg(rc, errstr, MAXRBUF);
1320 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1321 return false;
1322 }
1323
1324 res[nbytes_read] = '\0';
1325
1326 LOGF_DEBUG("RES <%s>", res);
1327
1328 return (res[0] == '#');
1329}
1330
1331bool NightCrawler::setSleepBrightness(uint8_t value)
1332{
1333 char cmd[16] = {0};
1334 char res[16] = {0};
1335
1336 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1337 char errstr[MAXRBUF];
1338
1339 snprintf(cmd, 16, "PL %03d#", value);
1340
1341 LOGF_DEBUG("CMD <%s>", cmd);
1342
1343 tcflush(PortFD, TCIOFLUSH);
1344
1345 if ( (rc = tty_write(PortFD, cmd, strlen(cmd), &nbytes_written)) != TTY_OK)
1346 {
1347 tty_error_msg(rc, errstr, MAXRBUF);
1348 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1349 return false;
1350 }
1351
1352 if ( (rc = tty_read(PortFD, res, 1, NIGHTCRAWLER_TIMEOUT, &nbytes_read)) != TTY_OK)
1353 {
1354 tty_error_msg(rc, errstr, MAXRBUF);
1355 LOGF_ERROR("%s error: %s.", __FUNCTION__, errstr);
1356 return false;
1357 }
1358
1359 res[nbytes_read] = '\0';
1360
1361 LOGF_DEBUG("RES <%s>", res);
1362
1363 return (res[0] == '#');
1364}
1365
1367{
1368 Focuser::saveConfigItems(fp);
1370
1371 BrightnessNP.save(fp);
1372 FocusStepDelayNP.save(fp);
1373 RotatorStepDelayNP.apply();
1374 AuxStepDelayNP.save(fp);
1375 CustomRotatorStepNP.save(fp);
1376
1377 return true;
1378}
1379
1381{
1382 if (findHome(0x02))
1383 {
1384 FindHomeSP.setState(IPS_BUSY);
1385 FindHomeSP[0].setState(ISS_ON);
1386 FindHomeSP.apply();
1387 LOG_WARN("Homing process can take up to 10 minutes. You cannot control the unit until the process is fully complete.");
1388 return IPS_BUSY;
1389 }
1390 else
1391 {
1392 FindHomeSP.setState(IPS_ALERT);
1393 FindHomeSP[0].setState(ISS_OFF);
1394 FindHomeSP.apply();
1395 LOG_ERROR("Failed to start homing process.");
1396 return IPS_ALERT;
1397 }
1398}
1399
1401{
1402 // Rotator move 0 to +180 degrees CCW
1403 // Rotator move 0 to -180 degrees CW
1404 // This is from looking at rotator from behind.
1405 const bool isReversed = ReverseRotatorSP[INDI_ENABLED].getState() == ISS_ON;
1406 auto newAngle = ( angle > 180 ? angle - 360 : angle);
1407 if (isReversed)
1408 newAngle *= -1;
1409
1410 auto newTarget = newAngle * m_RotatorTicksPerDegree;
1411 if (newTarget < RotatorAbsPosNP[0].getMin())
1412 newTarget = RotatorAbsPosNP[0].getMin();
1413 else if (newTarget > RotatorAbsPosNP[0].getMax())
1414 newTarget = RotatorAbsPosNP[0].getMax();
1415
1416 bool rc = gotoMotor(MOTOR_ROTATOR, static_cast<int32_t>(newTarget));
1417
1418 if (rc)
1419 {
1420 RotatorAbsPosNP.setState(IPS_BUSY);
1421 RotatorAbsPosNP.apply();
1422 return IPS_BUSY;
1423 }
1424
1425 return IPS_ALERT;
1426}
1427
1429{
1430 const bool isReversed = ReverseRotatorSP[INDI_ENABLED].getState() == ISS_ON;
1431 auto newAngle = ( angle > 180 ? angle - 360 : angle);
1432 if (isReversed)
1433 newAngle *= -1;
1434
1435 auto newTarget = newAngle * m_RotatorTicksPerDegree;
1436 if (newTarget < RotatorAbsPosNP[0].getMin())
1437 newTarget = RotatorAbsPosNP[0].getMin();
1438 else if (newTarget > RotatorAbsPosNP[0].getMax())
1439 newTarget = RotatorAbsPosNP[0].getMax();
1440
1441 return syncMotor(MOTOR_ROTATOR, static_cast<int32_t>(newTarget));
1442}
1443
1445{
1446 bool rc = stopMotor(MOTOR_ROTATOR);
1447 if (rc && RotatorAbsPosNP.getState() != IPS_OK)
1448 {
1449 RotatorAbsPosNP.setState(IPS_OK);
1450 RotatorAbsPosNP.apply();
1451 }
1452
1453 return rc;
1454}
1455
1457{
1458 // Immediately update the angle after reverse is set.
1459 if (enabled)
1460 GotoRotatorNP[0].setValue(range360(360 - (RotatorAbsPosNP[0].getValue() / m_RotatorTicksPerDegree)));
1461 else
1462 GotoRotatorNP[0].setValue(range360(RotatorAbsPosNP[0].getValue() / m_RotatorTicksPerDegree));
1464 return true;
1465}
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
bool isConnected() const
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 setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
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.
uint32_t getDriverInterface() const
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...
void addDebugControl()
Add Debug control to the driver.
INDI::PropertyNumber FocusRelPosNP
void SetCapability(uint32_t cap)
FI::SetCapability sets the focuser capabilities. All capabilities must be initialized.
INDI::PropertyNumber FocusSpeedNP
INDI::PropertyNumber FocusAbsPosNP
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Processes incoming new switch states from the client for this device. Handles updates to FocuserInter...
virtual bool updateProperties() override
Updates the state and definition of properties based on the current connection status....
virtual bool initProperties() override
Initializes the default properties for the focuser device. Sets up standard INDI properties,...
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Processes incoming new number values from the client for this device. Handles updates to FocuserInter...
Connection::Serial * serialConnection
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void save(FILE *f) const
save Save property to configuration file.
bool isNameMatch(const char *otherName) const
void fill(const char *device, const char *name, const char *label, const char *group, IPState state)
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)
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
void initProperties(const char *groupName)
Initialize Rotator properties. It is recommended to call this function within initProperties() of you...
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.
INDI::PropertySwitch HomeRotatorSP
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process Rotator number properties.
INDI::PropertySwitch ReverseRotatorSP
INDI::PropertyNumber GotoRotatorNP
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
Processes incoming new switch states from the client for this device. Handles updates to FocuserInter...
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
Performs a device-specific handshake after a connection is established. This method should be overrid...
static void abnormalDisconnectCallback(void *userpointer)
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Processes incoming new number values from the client for this device. Handles updates to FocuserInter...
virtual bool SyncRotator(double angle) override
SyncRotator Set current angle as the supplied angle without moving the rotator.
virtual bool saveConfigItems(FILE *fp) override
virtual IPState MoveRelFocuser(FocusDirection dir, uint32_t ticks) override
MoveFocuser the focuser to an relative position.
virtual bool updateProperties() override
Updates the state and definition of properties based on the current connection status....
virtual bool ReverseRotator(bool enabled) override
ReverseRotator Reverse the direction of the rotator. CW is usually the normal direction,...
virtual bool initProperties() override
Initializes the default properties for the focuser device. Sets up standard INDI properties,...
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:586
#define ROTATOR_TAB
Definition gemini.cpp:39
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
@ IP_WO
Definition indiapi.h:183
IPState
Property state.
Definition indiapi.h:158
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_1OFMANY
Definition indiapi.h:171
@ ISR_NOFMANY
Definition indiapi.h:173
@ ISR_ATMOST1
Definition indiapi.h:172
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:653
int tty_write(int fd, const char *buf, int nbytes, int *nbytes_written)
Writes a buffer to fd.
Definition indicom.c:418
int tty_read(int fd, char *buf, int nbytes, int timeout, int *nbytes_read)
read buffer from terminal
Definition indicom.c:548
double range360(double r)
range360 Limits an angle to be between 0-360 degrees.
Definition indicom.c:1321
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
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:490
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:398
void IDSetNumber(const INumberVectorProperty *nvp, const char *fmt,...)
#define MAXRBUF
Definition indidriver.c:52
#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 getDeviceName
#define NC_30_STEPS
#define NC_35_STEPS
#define AUX_TAB
#define NIGHTCRAWLER_TIMEOUT
#define NIGHTCRAWLER_THRESHOLD
#define NC_25_STEPS
uint16_t type
Definition pwc-ioctl.h:0
uint8_t cmd[4]
Definition pwc-ioctl.h:2
#define SETTINGS_TAB
Definition robofocus.cpp:44
char name[MAXINDINAME]
Definition indiapi.h:321