1 /*
2 MyFocuserPro2 Focuser
3 Copyright (c) 2019 Alan Townshend
4
5 Based on Moonlite focuser
6 Copyright (C) 2013-2019 Jasem Mutlaq (mutlaqja@ikarustech.com)
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Lesser General Public
10 License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public
19 License along with this library; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
22 */
23
24 #include "myfocuserpro2.h"
25 #include "connectionplugins/connectiontcp.h"
26 #include "indicom.h"
27
28 #include <cmath>
29 #include <cstring>
30 #include <memory>
31
32 #include <termios.h>
33 #include <unistd.h>
34
35 static std::unique_ptr<MyFocuserPro2> myFocuserPro2(new MyFocuserPro2());
36
MyFocuserPro2()37 MyFocuserPro2::MyFocuserPro2()
38 {
39 // Can move in Absolute & Relative motions, can AbortFocuser motion, and has variable speed.
40 FI::SetCapability(FOCUSER_CAN_ABS_MOVE | FOCUSER_CAN_REL_MOVE | FOCUSER_CAN_ABORT | FOCUSER_CAN_REVERSE |
41 FOCUSER_HAS_VARIABLE_SPEED |
42 FOCUSER_CAN_SYNC);
43
44 setSupportedConnections(CONNECTION_SERIAL | CONNECTION_TCP);
45
46 setVersion(0, 7);
47 }
48
initProperties()49 bool MyFocuserPro2::initProperties()
50 {
51 INDI::Focuser::initProperties();
52
53 FocusSpeedN[0].min = 0;
54 FocusSpeedN[0].max = 2;
55 FocusSpeedN[0].value = 1;
56
57 /* Relative and absolute movement */
58 FocusRelPosN[0].min = 0.;
59 FocusRelPosN[0].max = 50000.;
60 FocusRelPosN[0].value = 0.;
61 FocusRelPosN[0].step = 1000;
62
63 FocusAbsPosN[0].min = 0.;
64 FocusAbsPosN[0].max = 200000.;
65 FocusAbsPosN[0].value = 0.;
66 FocusAbsPosN[0].step = 1000;
67
68 FocusMaxPosN[0].min = 1024.;
69 FocusMaxPosN[0].max = 200000.;
70 FocusMaxPosN[0].value = 0.;
71 FocusMaxPosN[0].step = 1000;
72
73 //Backlash
74 BacklashInStepsN[0].min = 0;
75 BacklashInStepsN[0].max = 512;
76 BacklashInStepsN[0].value = 0;
77 BacklashInStepsN[0].step = 2;
78
79 BacklashOutStepsN[0].min = 0;
80 BacklashOutStepsN[0].max = 512;
81 BacklashOutStepsN[0].value = 0;
82 BacklashOutStepsN[0].step = 2;
83
84
85 // Backlash In
86 IUFillSwitch(&BacklashInS[INDI_ENABLED], "INDI_ENABLED", "On", ISS_OFF);
87 IUFillSwitch(&BacklashInS[INDI_DISABLED], "INDI_DISABLED", "Off", ISS_OFF);
88 IUFillSwitchVector(&BacklashInSP, BacklashInS, 2, getDeviceName(), "Backlash In", "", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 0,
89 IPS_IDLE);
90
91 IUFillNumber(&BacklashInStepsN[0], "Steps", "", "%3.0f", 0, 512, 2, 0);
92 IUFillNumberVector(&BacklashInStepsNP, BacklashInStepsN, 1, getDeviceName(), "Backlash-In", "", OPTIONS_TAB, IP_RW, 0,
93 IPS_IDLE);
94
95 // Backlash Out
96 IUFillSwitch(&BacklashOutS[INDI_ENABLED], "INDI_ENABLED", "On", ISS_OFF);
97 IUFillSwitch(&BacklashOutS[INDI_DISABLED], "INDI_DISABLED", "Off", ISS_OFF);
98 IUFillSwitchVector(&BacklashOutSP, BacklashOutS, 2, getDeviceName(), "Backlash Out", "", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 0,
99 IPS_IDLE);
100
101 IUFillNumber(&BacklashOutStepsN[0], "Steps", "", "%3.0f", 0, 512, 2, 0);
102 IUFillNumberVector(&BacklashOutStepsNP, BacklashOutStepsN, 1, getDeviceName(), "Backlash-Out", "", OPTIONS_TAB, IP_RW, 0,
103 IPS_IDLE);
104
105 // Focuser temperature
106 IUFillNumber(&TemperatureN[0], "TEMPERATURE", "Celsius", "%6.2f", -40, 80., 0., 0.);
107 IUFillNumberVector(&TemperatureNP, TemperatureN, 1, getDeviceName(), "FOCUS_TEMPERATURE", "Temperature", MAIN_CONTROL_TAB,
108 IP_RO, 0, IPS_IDLE);
109
110 // Temperature Settings
111 IUFillNumber(&TemperatureSettingN[0], "Coefficient", "", "%6.2f", 0, 50, 1, 0);
112 IUFillNumberVector(&TemperatureSettingNP, TemperatureSettingN, 1, getDeviceName(), "T. Settings", "", OPTIONS_TAB, IP_RW, 0,
113 IPS_IDLE);
114
115 // Compensate for temperature
116 IUFillSwitch(&TemperatureCompensateS[TEMP_COMPENSATE_ENABLE], "TEMP_COMPENSATE_ENABLE", "Enable", ISS_OFF);
117 IUFillSwitch(&TemperatureCompensateS[TEMP_COMPENSATE_DISABLE], "TEMP_COMPENSATE_DISABLE", "Disable", ISS_OFF);
118 IUFillSwitchVector(&TemperatureCompensateSP, TemperatureCompensateS, 2, getDeviceName(), "T. Compensate", "", OPTIONS_TAB,
119 IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
120
121 // Step Mode
122 IUFillSwitch(&StepModeS[FOCUS_THIRTYSECOND_STEP], "FOCUS_THIRTYSECOND_STEP", "1/32 Step", ISS_OFF);
123 IUFillSwitch(&StepModeS[FOCUS_SIXTEENTH_STEP], "FOCUS_SIXTEENTH_STEP", "1/16 Step", ISS_OFF);
124 IUFillSwitch(&StepModeS[FOCUS_EIGHTH_STEP], "FOCUS_EIGHTH_STEP", "1/8 Step", ISS_OFF);
125 IUFillSwitch(&StepModeS[FOCUS_QUARTER_STEP], "FOCUS_QUARTER_STEP", "1/4 Step", ISS_OFF);
126 IUFillSwitch(&StepModeS[FOCUS_HALF_STEP], "FOCUS_HALF_STEP", "1/2 Step", ISS_OFF);
127 IUFillSwitch(&StepModeS[FOCUS_FULL_STEP], "FOCUS_FULL_STEP", "Full Step", ISS_OFF);
128 IUFillSwitchVector(&StepModeSP, StepModeS, 6, getDeviceName(), "Step Mode", "", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 0,
129 IPS_IDLE);
130
131
132 IUFillSwitch(&CoilPowerS[COIL_POWER_ON], "COIL_POWER_ON", "On", ISS_OFF);
133 IUFillSwitch(&CoilPowerS[COIL_POWER_OFF], "COIL_POWER_OFF", "Off", ISS_OFF);
134 IUFillSwitchVector(&CoilPowerSP, CoilPowerS, 2, getDeviceName(), "Coil Power", "", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 0,
135 IPS_IDLE);
136
137 IUFillSwitch(&DisplayS[DISPLAY_OFF], "DISPLAY_OFF", "Off", ISS_OFF);
138 IUFillSwitch(&DisplayS[DISPLAY_ON], "DISPLAY_ON", "On", ISS_OFF);
139 IUFillSwitchVector(&DisplaySP, DisplayS, 2, getDeviceName(), "Display", "", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
140
141
142 IUFillSwitch(&GotoHomeS[0], "GOTO_HOME", "Go", ISS_OFF);
143 IUFillSwitchVector(&GotoHomeSP, GotoHomeS, 1, getDeviceName(), "Goto Home Position", "", MAIN_CONTROL_TAB, IP_RW,
144 ISR_ATMOST1, 0, IPS_IDLE);
145
146 setPollingPeriodRange(1000, 30000);
147 setDefaultPollingPeriod(1000);
148
149 tcpConnection->setDefaultHost("192.168.4.1");
150 tcpConnection->setDefaultPort(2020);
151
152 return true;
153 }
154
updateProperties()155 bool MyFocuserPro2::updateProperties()
156 {
157 INDI::Focuser::updateProperties();
158
159 if (isConnected())
160 {
161 defineProperty(&GotoHomeSP);
162 defineProperty(&TemperatureNP);
163 defineProperty(&TemperatureSettingNP);
164 defineProperty(&TemperatureCompensateSP);
165 defineProperty(&BacklashInSP);
166 defineProperty(&BacklashInStepsNP);
167 defineProperty(&BacklashOutSP);
168 defineProperty(&BacklashOutStepsNP);
169 defineProperty(&StepModeSP);
170 defineProperty(&DisplaySP);
171 defineProperty(&CoilPowerSP);
172
173 setTemperatureCelsius();
174
175 LOG_INFO("MyFocuserPro2 parameters updated, focuser ready for use.");
176 }
177 else
178 {
179 deleteProperty(GotoHomeSP.name);
180 deleteProperty(TemperatureNP.name);
181 deleteProperty(TemperatureSettingNP.name);
182 deleteProperty(TemperatureCompensateSP.name);
183 deleteProperty(BacklashInSP.name);
184 deleteProperty(BacklashInStepsNP.name);
185 deleteProperty(BacklashOutSP.name);
186 deleteProperty(BacklashOutStepsNP.name);
187 deleteProperty(StepModeSP.name);
188 deleteProperty(DisplaySP.name);
189 deleteProperty(CoilPowerSP.name);
190 }
191
192 return true;
193 }
194
Handshake()195 bool MyFocuserPro2::Handshake()
196 {
197 if (Ack())
198 {
199 LOG_INFO("MyFocuserPro2 is online. Getting focus parameters...");
200
201 getStartupValues();
202
203 return true;
204 }
205
206 LOG_INFO(
207 "Error retrieving data from MyFocuserPro2, please ensure MyFocuserPro2 controller is powered and the port is correct.");
208 return false;
209 }
210
getDefaultName()211 const char * MyFocuserPro2::getDefaultName()
212 {
213 return "MyFocuserPro2";
214 }
215
Ack()216 bool MyFocuserPro2::Ack()
217 {
218 int nbytes_written = 0, nbytes_read = 0, rc = -1;
219 char errstr[MAXRBUF];
220 char resp[5] = {0};
221 int firmWareVersion = 0;
222
223 tcflush(PortFD, TCIOFLUSH);
224
225 //Try to request the firmware version
226 //Test for success on transmission and response
227 //If either one fails, try again, up to 3 times, waiting 1 sec each time
228 //If that fails, then return false.
229 //If success then check the firmware version
230
231 int numChecks = 0;
232 bool success = false;
233 while(numChecks < 3 && !success)
234 {
235 numChecks++;
236 sleep(1); //wait 1 second between each test.
237
238 bool transmissionSuccess = (rc = tty_write(PortFD, ":03#", 4, &nbytes_written)) == TTY_OK;
239 if(!transmissionSuccess)
240 {
241 tty_error_msg(rc, errstr, MAXRBUF);
242 LOGF_ERROR("Handshake Attempt %i, tty transmission error: %s.", numChecks, errstr);
243 }
244
245 bool responseSuccess = (rc = tty_read(PortFD, resp, 5, ML_TIMEOUT, &nbytes_read)) == TTY_OK;
246 if(!responseSuccess)
247 {
248 tty_error_msg(rc, errstr, MAXRBUF);
249 LOGF_ERROR("Handshake Attempt %i, updatePosition response error: %s.", numChecks, errstr);
250 }
251
252 success = transmissionSuccess && responseSuccess;
253 }
254
255 if(!success)
256 {
257 LOG_INFO("Handshake failed after 3 attempts");
258 return false;
259 }
260
261 tcflush(PortFD, TCIOFLUSH);
262
263 rc = sscanf(resp, "F%d#", &firmWareVersion);
264
265 if (rc > 0)
266 {
267 // Minimum check only applicable to serial version
268 if (getActiveConnection()->type() == Connection::Interface::CONNECTION_SERIAL)
269 {
270 if(firmWareVersion >= MINIMUM_FIRMWARE_VERSION)
271 {
272 LOGF_INFO("MyFP2 reported firmware %d", firmWareVersion);
273 return true;
274
275 }
276 else
277 {
278 LOGF_ERROR("Invalid Firmware: focuser firmware version value %d, minimum supported is %d", firmWareVersion,
279 MINIMUM_FIRMWARE_VERSION );
280 }
281 }
282 else
283 {
284 LOG_INFO("Connection to network focuser is successful.");
285 return true;
286 }
287 }
288 else
289 {
290 LOGF_ERROR("Invalid Response: focuser firmware version value (%s)", resp);
291 }
292 return false;
293 }
294
readCoilPowerState()295 bool MyFocuserPro2::readCoilPowerState()
296 {
297 char res[ML_RES] = {0};
298
299 if (sendCommand(":11#", res) == false)
300 return false;
301
302 uint32_t temp = 0;
303
304 int rc = sscanf(res, "O%u#", &temp);
305
306 if (rc > 0)
307
308 if(temp == 0)
309 CoilPowerS[COIL_POWER_OFF].s = ISS_ON;
310 else if (temp == 1)
311 CoilPowerS[COIL_POWER_ON].s = ISS_ON;
312 else
313 {
314 LOGF_ERROR("Invalid Response: focuser Coil Power value (%s)", res);
315 return false;
316 }
317 else
318 {
319 LOGF_ERROR("Unknown error: focuser Coil Power value (%s)", res);
320 return false;
321 }
322
323
324 return true;
325 }
326
readReverseDirection()327 bool MyFocuserPro2::readReverseDirection()
328 {
329 char res[ML_RES] = {0};
330
331 if (sendCommand(":13#", res) == false)
332 return false;
333
334 uint32_t temp = 0;
335
336 int rc = sscanf(res, "R%u#", &temp);
337
338 if (rc > 0)
339
340 if(temp == 0)
341 {
342 FocusReverseS[INDI_DISABLED].s = ISS_ON;
343 }
344 else if (temp == 1)
345 {
346 FocusReverseS[INDI_ENABLED].s = ISS_ON;
347 }
348 else
349 {
350 LOGF_ERROR("Invalid Response: focuser Reverse direction value (%s)", res);
351 return false;
352 }
353 else
354 {
355 LOGF_ERROR("Unknown error: focuser Reverse direction value (%s)", res);
356 return false;
357 }
358
359 return true;
360 }
361
readStepMode()362 bool MyFocuserPro2::readStepMode()
363 {
364 char res[ML_RES] = {0};
365
366 if (sendCommand(":29#", res) == false)
367 return false;
368
369 if (strcmp(res, "S1#") == 0)
370 StepModeS[FOCUS_FULL_STEP].s = ISS_ON;
371 else if (strcmp(res, "S2#") == 0)
372 StepModeS[FOCUS_HALF_STEP].s = ISS_ON;
373 else if (strcmp(res, "S4#") == 0)
374 StepModeS[FOCUS_QUARTER_STEP].s = ISS_ON;
375 else if (strcmp(res, "S8#") == 0)
376 StepModeS[FOCUS_EIGHTH_STEP].s = ISS_ON;
377 else if (strcmp(res, "S16#") == 0)
378 StepModeS[FOCUS_SIXTEENTH_STEP].s = ISS_ON;
379 else if (strcmp(res, "S32#") == 0)
380 StepModeS[FOCUS_THIRTYSECOND_STEP].s = ISS_ON;
381 else
382 {
383 LOGF_ERROR("Unknown error: focuser Step Mode value (%s)", res);
384 return false;
385 }
386
387 return true;
388 }
389
readTemperature()390 bool MyFocuserPro2::readTemperature()
391 {
392 char res[ML_RES] = {0};
393
394 if (sendCommand(":06#", res) == false)
395 return false;
396
397 double temp = 0;
398 int rc = sscanf(res, "Z%lf#", &temp);
399 if (rc > 0)
400 // Signed hex
401 TemperatureN[0].value = temp;
402 else
403 {
404 LOGF_ERROR("Unknown error: focuser temperature value (%s)", res);
405 return false;
406 }
407
408 return true;
409 }
410
readTempCompensateEnable()411 bool MyFocuserPro2::readTempCompensateEnable()
412 {
413 char res[ML_RES] = {0};
414
415 if (sendCommand(":24#", res) == false)
416 return false;
417
418 uint32_t temp = 0;
419
420 int rc = sscanf(res, "1%u#", &temp);
421
422 if (rc > 0)
423
424 if(temp == 0)
425 TemperatureCompensateS[TEMP_COMPENSATE_DISABLE].s = ISS_ON;
426 else if (temp == 1)
427 TemperatureCompensateS[TEMP_COMPENSATE_ENABLE].s = ISS_ON;
428 else
429 {
430 LOGF_ERROR("Invalid Response: focuser T.Compensate value (%s)", res);
431 return false;
432 }
433 else
434 {
435 LOGF_ERROR("Unknown error: focuser T.Compensate value (%s)", res);
436 return false;
437 }
438
439 return true;
440 }
441
442
readPosition()443 bool MyFocuserPro2::readPosition()
444 {
445 char res[ML_RES] = {0};
446
447 if (sendCommand(":00#", res) == false)
448 return false;
449
450 int32_t pos;
451 int rc = sscanf(res, "%*c%d#", &pos);
452
453 if (rc > 0)
454 FocusAbsPosN[0].value = pos;
455 else
456 {
457 LOGF_ERROR("Unknown error: focuser position value (%s)", res);
458 return false;
459 }
460
461 return true;
462 }
463
readTempeartureCoefficient()464 bool MyFocuserPro2::readTempeartureCoefficient()
465 {
466 char res[ML_RES] = {0};
467
468 if (sendCommand(":26#", res) == false)
469 return false;
470
471 int32_t val;
472 int rc = sscanf(res, "B%d#", &val);
473
474 if (rc > 0)
475 TemperatureSettingN[0].value = val;
476 else
477 {
478 LOGF_ERROR("Unknown error: Temperature Coefficient value (%s)", res);
479 return false;
480 }
481
482 return true;
483 }
484
readSpeed()485 bool MyFocuserPro2::readSpeed()
486 {
487 char res[ML_RES] = {0};
488
489 if (sendCommand(":43#", res) == false)
490 return false;
491
492 int speed = 0;
493 int rc = sscanf(res, "C%d#", &speed);
494
495 if (rc > 0)
496 {
497 FocusSpeedN[0].value = speed;
498 }
499 else
500 {
501 LOGF_ERROR("Unknown error: focuser speed value (%s)", res);
502 return false;
503 }
504
505 return true;
506 }
507
readMaxPos()508 bool MyFocuserPro2::readMaxPos()
509 {
510 char res[ML_RES] = {0};
511
512 if (sendCommand(":08#", res) == false)
513 return false;
514
515 uint32_t maxPos = 0;
516 int rc = sscanf(res, "M%u#", &maxPos);
517
518 if (rc > 0)
519 {
520 FocusMaxPosN[0].value = maxPos;
521 Focuser::SyncPresets(maxPos);
522 }
523 else
524 {
525 LOGF_ERROR("Unknown error: focuser max position value (%s)", res);
526 return false;
527 }
528
529 return true;
530 }
531
readBacklashInSteps()532 bool MyFocuserPro2::readBacklashInSteps()
533 {
534 char res[ML_RES] = {0};
535
536 if (sendCommand(":78#", res) == false)
537 return false;
538
539 uint32_t backlash = 0;
540 int rc = sscanf(res, "6%u#", &backlash);
541
542 if (rc > 0)
543 {
544 BacklashInStepsN[0].value = backlash;
545 }
546 else
547 {
548 BacklashInStepsN[0].value = 0;
549 LOGF_ERROR("Unknown error: focuser Backlash IN value (%s)", res);
550 return false;
551 }
552
553 return true;
554 }
555
readBacklashInEnabled()556 bool MyFocuserPro2::readBacklashInEnabled()
557 {
558 char res[ML_RES] = {0};
559
560 if (sendCommand(":74#", res) == false)
561 return false;
562
563 uint32_t temp = 0;
564 int rc = sscanf(res, "4%u#", &temp);
565
566 if (rc > 0)
567 {
568 if(temp == 0)
569 BacklashInS[INDI_DISABLED].s = ISS_ON;
570 else if (temp == 1)
571 BacklashInS[INDI_ENABLED].s = ISS_ON;
572 else
573 LOGF_ERROR("Unknown Repsonse: focuser Backlash IN enabled (%s)", res);
574 return false;
575 }
576 else
577 {
578 LOGF_ERROR("Unknown error: focuser Backlash IN enabled (%s)", res);
579 return false;
580 }
581 }
582
readBacklashOutSteps()583 bool MyFocuserPro2::readBacklashOutSteps()
584 {
585 char res[ML_RES] = {0};
586
587 if (sendCommand(":80#", res) == false)
588 return false;
589
590 uint32_t backlash = 0;
591 int rc = sscanf(res, "7%u#", &backlash);
592
593 if (rc > 0)
594 {
595 BacklashOutStepsN[0].value = backlash;
596 }
597 else
598 {
599 LOGF_ERROR("Unknown error: focuser Backlash OUT value (%s)", res);
600 return false;
601 }
602
603 return true;
604 }
605
readBacklashOutEnabled()606 bool MyFocuserPro2::readBacklashOutEnabled()
607 {
608 char res[ML_RES] = {0};
609
610 if (sendCommand(":76#", res) == false)
611 return false;
612
613 uint32_t temp = 0;
614 int rc = sscanf(res, "5%u#", &temp);
615
616 if (rc > 0)
617 {
618 if(temp == 0)
619 BacklashOutS[INDI_DISABLED].s = ISS_ON;
620 else if (temp == 1)
621 BacklashOutS[INDI_ENABLED].s = ISS_ON;
622 else
623 LOGF_ERROR("Unknown response: focuser Backlash OUT enabled (%s)", res);
624 return false;
625 }
626 else
627 {
628 LOGF_ERROR("Unknown error: focuser Backlash OUT enabled (%s)", res);
629 return false;
630 }
631
632 }
633
readDisplayVisible()634 bool MyFocuserPro2::readDisplayVisible()
635 {
636 char res[ML_RES] = {0};
637
638 if (sendCommand(":37#", res) == false)
639 return false;
640
641 uint32_t temp = 0;
642
643 int rc = sscanf(res, "D%u#", &temp);
644
645 if (rc > 0)
646 {
647 if(temp == 0)
648 DisplayS[DISPLAY_OFF].s = ISS_ON;
649 else if (temp == 1)
650 DisplayS[DISPLAY_ON].s = ISS_ON;
651 else
652 {
653 LOGF_ERROR("Invalid Response: focuser Display value (%s)", res);
654 return false;
655 }
656 }
657 else
658 {
659 LOGF_ERROR("Unknown error: focuser Display value (%s)", res);
660 return false;
661 }
662
663 return true;
664 }
665
isMoving()666 bool MyFocuserPro2::isMoving()
667 {
668 char res[ML_RES] = {0};
669
670 if (sendCommand(":01#", res) == false)
671 return false;
672
673 uint32_t temp = 0;
674
675 int rc = sscanf(res, "I%u#", &temp);
676
677 if (rc > 0)
678 {
679 if(temp == 0)
680 return false;
681 else if (temp == 1)
682 return true;
683 else
684 {
685 LOGF_ERROR("Invalid Response: focuser isMoving value (%s)", res);
686 return false;
687 }
688 }
689 else
690 {
691 LOGF_ERROR("Unknown error: focuser isMoving value (%s)", res);
692 return false;
693 }
694 }
695
696
setTemperatureCelsius()697 bool MyFocuserPro2::setTemperatureCelsius()
698 {
699 char cmd[ML_RES] = {0};
700 snprintf(cmd, ML_RES, ":161#");
701 return sendCommand(cmd);
702 }
703
setTemperatureCoefficient(double coefficient)704 bool MyFocuserPro2::setTemperatureCoefficient(double coefficient)
705 {
706 char cmd[ML_RES] = {0};
707 int coeff = coefficient;
708 snprintf(cmd, ML_RES, ":22%d#", coeff);
709 return sendCommand(cmd);
710 }
711
SyncFocuser(uint32_t ticks)712 bool MyFocuserPro2::SyncFocuser(uint32_t ticks)
713 {
714 char cmd[ML_RES] = {0};
715 snprintf(cmd, ML_RES, ":31%u#", ticks);
716 return sendCommand(cmd);
717 }
718
MoveFocuser(uint32_t position)719 bool MyFocuserPro2::MoveFocuser(uint32_t position)
720 {
721 char cmd[ML_RES] = {0};
722 if(isMoving())
723 {
724 AbortFocuser();
725 }
726 snprintf(cmd, ML_RES, ":05%u#", position);
727 return sendCommand(cmd);
728 }
729
setBacklashInSteps(int16_t steps)730 bool MyFocuserPro2::setBacklashInSteps(int16_t steps)
731 {
732 char cmd[ML_RES] = {0};
733 snprintf(cmd, ML_RES, ":77%d#", steps);
734 return sendCommand(cmd);
735 }
736
setBacklashInEnabled(bool enabled)737 bool MyFocuserPro2::setBacklashInEnabled(bool enabled)
738 {
739 char cmd[ML_RES] = {0};
740 snprintf(cmd, ML_RES, ":73%c#", enabled ? '1' : '0');
741 return sendCommand(cmd);
742 }
743
setBacklashOutSteps(int16_t steps)744 bool MyFocuserPro2::setBacklashOutSteps(int16_t steps)
745 {
746 char cmd[ML_RES] = {0};
747 snprintf(cmd, ML_RES, ":79%d#", steps);
748 return sendCommand(cmd);
749 }
750
setBacklashOutEnabled(bool enabled)751 bool MyFocuserPro2::setBacklashOutEnabled(bool enabled)
752 {
753 char cmd[ML_RES] = {0};
754 snprintf(cmd, ML_RES, ":75%c#", enabled ? '1' : '0');
755 return sendCommand(cmd);
756 }
757
setCoilPowerState(CoilPower enable)758 bool MyFocuserPro2::setCoilPowerState(CoilPower enable)
759 {
760 char cmd[ML_RES] = {0};
761 snprintf(cmd, ML_RES, ":12%d#", static_cast<int>(enable));
762 return sendCommand(cmd);
763 }
764
765
ReverseFocuser(bool enable)766 bool MyFocuserPro2::ReverseFocuser(bool enable)
767 {
768 char cmd[ML_RES] = {0};
769 snprintf(cmd, ML_RES, ":14%c#", enable ? '1' : '0');
770 return sendCommand(cmd);
771 }
772
773
setDisplayVisible(DisplayMode enable)774 bool MyFocuserPro2::setDisplayVisible(DisplayMode enable)
775 {
776 char cmd[ML_RES] = {0};
777 snprintf(cmd, ML_RES, ":36%d#", enable);
778 return sendCommand(cmd);
779 }
780
setGotoHome()781 bool MyFocuserPro2::setGotoHome()
782 {
783 char cmd[ML_RES] = {0};
784 if(isMoving())
785 {
786 AbortFocuser();
787 }
788 snprintf(cmd, ML_RES, ":28#");
789 return sendCommand(cmd);
790 }
791
setStepMode(FocusStepMode mode)792 bool MyFocuserPro2::setStepMode(FocusStepMode mode)
793 {
794 char cmd[ML_RES] = {0};
795 int setMode = 1 << static_cast<int>(mode);
796 snprintf(cmd, ML_RES, ":30%02d#", setMode);
797 return sendCommand(cmd);
798 }
799
setSpeed(uint16_t speed)800 bool MyFocuserPro2::setSpeed(uint16_t speed)
801 {
802 char cmd[ML_RES] = {0};
803 snprintf(cmd, ML_RES, ":150%d#", speed);
804 return sendCommand(cmd);
805 }
806
setTemperatureCompensation(bool enable)807 bool MyFocuserPro2::setTemperatureCompensation(bool enable)
808 {
809 char cmd[ML_RES] = {0};
810 snprintf(cmd, ML_RES, ":23%c#", enable ? '1' : '0');
811 return sendCommand(cmd);
812 }
813
ISNewSwitch(const char * dev,const char * name,ISState * states,char * names[],int n)814 bool MyFocuserPro2::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
815 {
816 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
817 {
818 // Focus Step Mode
819 if (strcmp(StepModeSP.name, name) == 0)
820 {
821 int current_mode = IUFindOnSwitchIndex(&StepModeSP);
822
823 IUUpdateSwitch(&StepModeSP, states, names, n);
824
825 int target_mode = IUFindOnSwitchIndex(&StepModeSP);
826
827 if (current_mode == target_mode)
828 {
829 StepModeSP.s = IPS_OK;
830 IDSetSwitch(&StepModeSP, nullptr);
831 }
832
833 bool rc = setStepMode(static_cast<FocusStepMode>(target_mode));
834 if (!rc)
835 {
836 IUResetSwitch(&StepModeSP);
837 StepModeS[current_mode].s = ISS_ON;
838 StepModeSP.s = IPS_ALERT;
839 IDSetSwitch(&StepModeSP, nullptr);
840 return false;
841 }
842
843 StepModeSP.s = IPS_OK;
844 IDSetSwitch(&StepModeSP, nullptr);
845 return true;
846 }
847
848 // Goto Home Position
849 if (strcmp(GotoHomeSP.name, name) == 0)
850 {
851 bool rc = setGotoHome();
852 if (!rc)
853 {
854 IUResetSwitch(&GotoHomeSP);
855 CoilPowerSP.s = IPS_ALERT;
856 IDSetSwitch(&GotoHomeSP, nullptr);
857 return false;
858 }
859
860 GotoHomeSP.s = IPS_OK;
861 IDSetSwitch(&GotoHomeSP, nullptr);
862 return true;
863 }
864
865 // Coil Power Mode
866 if (strcmp(CoilPowerSP.name, name) == 0)
867 {
868 int current_mode = IUFindOnSwitchIndex(&CoilPowerSP);
869
870 IUUpdateSwitch(&CoilPowerSP, states, names, n);
871
872 int target_mode = IUFindOnSwitchIndex(&CoilPowerSP);
873
874 if (current_mode == target_mode)
875 {
876 CoilPowerSP.s = IPS_OK;
877 IDSetSwitch(&CoilPowerSP, nullptr);
878 }
879
880 bool rc = setCoilPowerState(static_cast<CoilPower>(target_mode));
881 if (!rc)
882 {
883 IUResetSwitch(&CoilPowerSP);
884 CoilPowerS[current_mode].s = ISS_ON;
885 CoilPowerSP.s = IPS_ALERT;
886 IDSetSwitch(&CoilPowerSP, nullptr);
887 return false;
888 }
889
890 CoilPowerSP.s = IPS_OK;
891 IDSetSwitch(&CoilPowerSP, nullptr);
892 return true;
893 }
894
895
896 // Display Control
897 if (strcmp(DisplaySP.name, name) == 0)
898 {
899 int current_mode = IUFindOnSwitchIndex(&DisplaySP);
900
901 IUUpdateSwitch(&DisplaySP, states, names, n);
902
903 int target_mode = IUFindOnSwitchIndex(&DisplaySP);
904
905 if (current_mode == target_mode)
906 {
907 DisplaySP.s = IPS_OK;
908 IDSetSwitch(&DisplaySP, nullptr);
909 }
910
911 bool rc = setDisplayVisible(static_cast<DisplayMode>(target_mode));
912 if (!rc)
913 {
914 IUResetSwitch(&DisplaySP);
915 DisplayS[current_mode].s = ISS_ON;
916 DisplaySP.s = IPS_ALERT;
917 IDSetSwitch(&DisplaySP, nullptr);
918 return false;
919 }
920
921 DisplaySP.s = IPS_OK;
922 IDSetSwitch(&DisplaySP, nullptr);
923 return true;
924 }
925
926 // Backlash In Enable
927 if (strcmp(BacklashInSP.name, name) == 0)
928 {
929 int current_mode = IUFindOnSwitchIndex(&BacklashInSP);
930
931 IUUpdateSwitch(&BacklashInSP, states, names, n);
932
933 int target_mode = IUFindOnSwitchIndex(&BacklashInSP);
934
935 if (current_mode == target_mode)
936 {
937 BacklashInSP.s = IPS_OK;
938 IDSetSwitch(&BacklashInSP, nullptr);
939 }
940
941 bool rc = setBacklashInEnabled(target_mode == INDI_ENABLED);
942 if (!rc)
943 {
944 IUResetSwitch(&BacklashInSP);
945 BacklashInS[current_mode].s = ISS_ON;
946 BacklashInSP.s = IPS_ALERT;
947 IDSetSwitch(&BacklashInSP, nullptr);
948 return false;
949 }
950
951 BacklashInSP.s = IPS_OK;
952 IDSetSwitch(&BacklashInSP, nullptr);
953 return true;
954 }
955
956 // Backlash Out Enable
957 if (strcmp(BacklashOutSP.name, name) == 0)
958 {
959 int current_mode = IUFindOnSwitchIndex(&BacklashOutSP);
960
961 IUUpdateSwitch(&BacklashOutSP, states, names, n);
962
963 int target_mode = IUFindOnSwitchIndex(&BacklashOutSP);
964
965 if (current_mode == target_mode)
966 {
967 BacklashOutSP.s = IPS_OK;
968 IDSetSwitch(&BacklashOutSP, nullptr);
969 }
970
971 bool rc = setBacklashOutEnabled(target_mode == INDI_ENABLED);
972 if (!rc)
973 {
974 IUResetSwitch(&BacklashOutSP);
975 BacklashOutS[current_mode].s = ISS_ON;
976 BacklashOutSP.s = IPS_ALERT;
977 IDSetSwitch(&BacklashOutSP, nullptr);
978 return false;
979 }
980
981 BacklashOutSP.s = IPS_OK;
982 IDSetSwitch(&BacklashOutSP, nullptr);
983 return true;
984 }
985
986 // Temperature Compensation Mode
987 if (strcmp(TemperatureCompensateSP.name, name) == 0)
988 {
989 int last_index = IUFindOnSwitchIndex(&TemperatureCompensateSP);
990 IUUpdateSwitch(&TemperatureCompensateSP, states, names, n);
991
992 bool rc = setTemperatureCompensation((TemperatureCompensateS[0].s == ISS_ON));
993
994 if (!rc)
995 {
996 TemperatureCompensateSP.s = IPS_ALERT;
997 IUResetSwitch(&TemperatureCompensateSP);
998 TemperatureCompensateS[last_index].s = ISS_ON;
999 IDSetSwitch(&TemperatureCompensateSP, nullptr);
1000 return false;
1001 }
1002
1003 TemperatureCompensateSP.s = IPS_OK;
1004 IDSetSwitch(&TemperatureCompensateSP, nullptr);
1005 return true;
1006 }
1007 }
1008
1009 return INDI::Focuser::ISNewSwitch(dev, name, states, names, n);
1010 }
1011
ISNewNumber(const char * dev,const char * name,double values[],char * names[],int n)1012 bool MyFocuserPro2::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
1013 {
1014 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
1015 {
1016 // Temperature Settings
1017 if (strcmp(name, TemperatureSettingNP.name) == 0)
1018 {
1019 IUUpdateNumber(&TemperatureSettingNP, values, names, n);
1020 if (!setTemperatureCoefficient(TemperatureSettingN[0].value))
1021 {
1022 TemperatureSettingNP.s = IPS_ALERT;
1023 IDSetNumber(&TemperatureSettingNP, nullptr);
1024 return false;
1025 }
1026
1027 TemperatureSettingNP.s = IPS_OK;
1028 IDSetNumber(&TemperatureSettingNP, nullptr);
1029 return true;
1030 }
1031
1032 // Backlash In
1033 if (strcmp(name, BacklashInStepsNP.name) == 0)
1034 {
1035 IUUpdateNumber(&BacklashInStepsNP, values, names, n);
1036 if (!setBacklashInSteps(BacklashInStepsN[0].value))
1037 {
1038 BacklashInStepsNP.s = IPS_ALERT;
1039 IDSetNumber(&BacklashInStepsNP, nullptr);
1040 return false;
1041 }
1042
1043 BacklashInStepsNP.s = IPS_OK;
1044 IDSetNumber(&BacklashInStepsNP, nullptr);
1045 return true;
1046 }
1047
1048 // Backlash Out
1049 if (strcmp(name, BacklashOutStepsNP.name) == 0)
1050 {
1051 IUUpdateNumber(&BacklashOutStepsNP, values, names, n);
1052 if (!setBacklashOutSteps(BacklashOutStepsN[0].value))
1053 {
1054 BacklashOutStepsNP.s = IPS_ALERT;
1055 IDSetNumber(&BacklashOutStepsNP, nullptr);
1056 return false;
1057 }
1058
1059 BacklashOutStepsNP.s = IPS_OK;
1060 IDSetNumber(&BacklashOutStepsNP, nullptr);
1061 return true;
1062 }
1063
1064 }
1065
1066 return INDI::Focuser::ISNewNumber(dev, name, values, names, n);
1067 }
1068
getStartupValues()1069 void MyFocuserPro2::getStartupValues()
1070 {
1071 readMaxPos();
1072 readPosition();
1073 readTemperature();
1074 readTempeartureCoefficient();
1075 readSpeed();
1076 readTempCompensateEnable();
1077 readStepMode();
1078 readCoilPowerState();
1079 readDisplayVisible();
1080 readReverseDirection();
1081 readBacklashInEnabled();
1082 readBacklashOutEnabled();
1083 readBacklashInSteps();
1084 readBacklashOutSteps();
1085 }
1086
SetFocuserSpeed(int speed)1087 bool MyFocuserPro2::SetFocuserSpeed(int speed)
1088 {
1089 return setSpeed(speed);
1090 }
1091
1092
SetFocuserMaxPosition(uint32_t maxPos)1093 bool MyFocuserPro2::SetFocuserMaxPosition(uint32_t maxPos)
1094 {
1095 char cmd[ML_RES] = {0};
1096
1097 snprintf(cmd, ML_RES, ":07%06d#", maxPos);
1098
1099 if(sendCommand(cmd))
1100 {
1101 Focuser::SyncPresets(maxPos);
1102
1103 return true;
1104 }
1105 return false;
1106 }
1107
MoveFocuser(FocusDirection dir,int speed,uint16_t duration)1108 IPState MyFocuserPro2::MoveFocuser(FocusDirection dir, int speed, uint16_t duration)
1109 {
1110 if (speed != static_cast<int>(FocusSpeedN[0].value))
1111 {
1112 if (!setSpeed(speed))
1113 return IPS_ALERT;
1114 }
1115
1116 // either go all the way in or all the way out
1117 // then use timer to stop
1118 if (dir == FOCUS_INWARD)
1119 MoveFocuser(0);
1120 else
1121 MoveFocuser(FocusMaxPosN[0].value);
1122
1123 IEAddTimer(duration, &MyFocuserPro2::timedMoveHelper, this);
1124 return IPS_BUSY;
1125 }
1126
timedMoveHelper(void * context)1127 void MyFocuserPro2::timedMoveHelper(void * context)
1128 {
1129 static_cast<MyFocuserPro2 *>(context)->timedMoveCallback();
1130 }
1131
timedMoveCallback()1132 void MyFocuserPro2::timedMoveCallback()
1133 {
1134 AbortFocuser();
1135 FocusAbsPosNP.s = IPS_IDLE;
1136 FocusRelPosNP.s = IPS_IDLE;
1137 FocusTimerNP.s = IPS_IDLE;
1138 FocusTimerN[0].value = 0;
1139 IDSetNumber(&FocusAbsPosNP, nullptr);
1140 IDSetNumber(&FocusRelPosNP, nullptr);
1141 IDSetNumber(&FocusTimerNP, nullptr);
1142 }
1143
MoveAbsFocuser(uint32_t targetTicks)1144 IPState MyFocuserPro2::MoveAbsFocuser(uint32_t targetTicks)
1145 {
1146 targetPos = targetTicks;
1147
1148 if (!MoveFocuser(targetPos))
1149 return IPS_ALERT;
1150
1151 return IPS_BUSY;
1152 }
1153
MoveRelFocuser(FocusDirection dir,uint32_t ticks)1154 IPState MyFocuserPro2::MoveRelFocuser(FocusDirection dir, uint32_t ticks)
1155 {
1156 int32_t newPosition = 0;
1157
1158 if (dir == FOCUS_INWARD)
1159 newPosition = FocusAbsPosN[0].value - ticks;
1160 else
1161 newPosition = FocusAbsPosN[0].value + ticks;
1162
1163 // Clamp
1164 newPosition = std::max(0, std::min(static_cast<int32_t>(FocusAbsPosN[0].max), newPosition));
1165 if (!MoveFocuser(newPosition))
1166 return IPS_ALERT;
1167
1168 FocusRelPosN[0].value = ticks;
1169 FocusRelPosNP.s = IPS_BUSY;
1170
1171 return IPS_BUSY;
1172 }
1173
TimerHit()1174 void MyFocuserPro2::TimerHit()
1175 {
1176 if (!isConnected())
1177 {
1178 SetTimer(getCurrentPollingPeriod());
1179 return;
1180 }
1181
1182 bool rc = readPosition();
1183 if (rc)
1184 {
1185 if (fabs(lastPos - FocusAbsPosN[0].value) > 5)
1186 {
1187 IDSetNumber(&FocusAbsPosNP, nullptr);
1188 lastPos = FocusAbsPosN[0].value;
1189 }
1190 }
1191
1192 rc = readTemperature();
1193 if (rc)
1194 {
1195 if (fabs(lastTemperature - TemperatureN[0].value) >= 0.5)
1196 {
1197 IDSetNumber(&TemperatureNP, nullptr);
1198 lastTemperature = TemperatureN[0].value;
1199 }
1200 }
1201
1202 if (FocusAbsPosNP.s == IPS_BUSY || FocusRelPosNP.s == IPS_BUSY)
1203 {
1204 if (!isMoving())
1205 {
1206 FocusAbsPosNP.s = IPS_OK;
1207 FocusRelPosNP.s = IPS_OK;
1208 IDSetNumber(&FocusAbsPosNP, nullptr);
1209 IDSetNumber(&FocusRelPosNP, nullptr);
1210 lastPos = FocusAbsPosN[0].value;
1211 LOG_INFO("Focuser reached requested position.");
1212 }
1213 }
1214
1215 SetTimer(getCurrentPollingPeriod());
1216 }
1217
AbortFocuser()1218 bool MyFocuserPro2::AbortFocuser()
1219 {
1220 return sendCommand(":27#");
1221 }
1222
saveConfigItems(FILE * fp)1223 bool MyFocuserPro2::saveConfigItems(FILE * fp)
1224 {
1225 Focuser::saveConfigItems(fp);
1226
1227 IUSaveConfigSwitch(fp, &StepModeSP);
1228
1229 return true;
1230 }
1231
sendCommand(const char * cmd,char * res)1232 bool MyFocuserPro2::sendCommand(const char * cmd, char * res)
1233 {
1234 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1235
1236 tcflush(PortFD, TCIOFLUSH);
1237
1238 LOGF_DEBUG("CMD <%s>", cmd);
1239
1240 if ((rc = tty_write_string(PortFD, cmd, &nbytes_written)) != TTY_OK)
1241 {
1242 char errstr[MAXRBUF] = {0};
1243 tty_error_msg(rc, errstr, MAXRBUF);
1244 LOGF_ERROR("Serial write error: %s.", errstr);
1245 return false;
1246 }
1247
1248 if (res == nullptr)
1249 {
1250 tcdrain(PortFD);
1251 return true;
1252 }
1253
1254 if ((rc = tty_nread_section(PortFD, res, ML_RES, ML_DEL, ML_TIMEOUT, &nbytes_read)) != TTY_OK)
1255 {
1256 char errstr[MAXRBUF] = {0};
1257 tty_error_msg(rc, errstr, MAXRBUF);
1258 LOGF_ERROR("Serial read error: %s.", errstr);
1259 return false;
1260 }
1261
1262 LOGF_DEBUG("RES <%s>", res);
1263
1264 tcflush(PortFD, TCIOFLUSH);
1265
1266 return true;
1267 }
1268